[6/6] winemenubuilder: Implement OS X application bundles.
Per Johansson
per at morth.org
Wed Oct 24 15:55:39 CDT 2012
Icon extraction had to be moved until after main file is created.
Make icon extraction into a two step process.
Since last time:
Rebased
Always call the launcher script "winelauncher"
Allow specific environment varables to specify output directories.
---
programs/winemenubuilder/Makefile.in | 2 +
programs/winemenubuilder/appbundler.c | 992 +++++++++++++++++++++
programs/winemenubuilder/tests/Makefile.in | 1 +
programs/winemenubuilder/tests/appbundle.c | 244 +++++
.../winemenubuilder/tests/winemenubuilder_test.h | 17 +
programs/winemenubuilder/tests/xdg.c | 5 +-
programs/winemenubuilder/winemenubuilder.c | 302 +++----
programs/winemenubuilder/winemenubuilder.h | 11 +-
programs/winemenubuilder/xdg.c | 24 +-
9 files changed, 1401 insertions(+), 197 deletions(-)
create mode 100644 programs/winemenubuilder/appbundler.c
create mode 100644 programs/winemenubuilder/tests/appbundle.c
create mode 100644 programs/winemenubuilder/tests/winemenubuilder_test.h
diff --git a/programs/winemenubuilder/Makefile.in b/programs/winemenubuilder/Makefile.in
index fd68ae2..147fa60 100644
--- a/programs/winemenubuilder/Makefile.in
+++ b/programs/winemenubuilder/Makefile.in
@@ -1,8 +1,10 @@
MODULE = winemenubuilder.exe
APPMODE = -mwindows -municode
IMPORTS = uuid windowscodecs shell32 shlwapi ole32 user32 advapi32
+EXTRALIBS = @COREFOUNDATIONLIB@ @APPLICATIONSERVICESLIB@
C_SRCS = \
+ appbundler.c \
winemenubuilder.c \
xdg.c
diff --git a/programs/winemenubuilder/appbundler.c b/programs/winemenubuilder/appbundler.c
new file mode 100644
index 0000000..eefd2ba
--- /dev/null
+++ b/programs/winemenubuilder/appbundler.c
@@ -0,0 +1,992 @@
+/*
+ * Winemenubuilder support for Mac OS X Application Bundles
+ *
+ * Copyright 2011 Steven Edwards
+ * Copyright 2011 - 2012 Per Johansson
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+ *
+ *
+ * NOTES: An Application Bundle generally has the following layout
+ *
+ * foo.app/Contents
+ * foo.app/Contents/Info.plist
+ * foo.app/Contents/MacOS/foo (can be script or real binary)
+ * foo.app/Contents/Resources/appIcon.icns (Apple Icon format)
+ * foo.app/Contents/Resources/English.lproj/infoPlist.strings
+ * foo.app/Contents/Resources/English.lproj/MainMenu.nib (Menu Layout)
+ *
+ * There can be more to a bundle depending on the target, what resources
+ * it contains and what the target platform but this simplifed format
+ * is all we really need for now for Wine.
+ *
+ * TODO:
+ * - Convert to using CoreFoundation API rather than standard unix file ops
+ * - See if there is anything else in the rsrc section of the target that
+ * we might want to dump in a *.plist. Version information for the target
+ * and or Wine Version information come to mind.
+ * - sha1hash of target application in bundle plist
+ */
+
+#ifdef __APPLE__
+
+#include "config.h"
+#include "wine/port.h"
+
+#include <stdio.h>
+#include <errno.h>
+
+#include <CoreFoundation/CoreFoundation.h>
+#ifdef HAVE_APPLICATIONSERVICES_APPLICATIONSERVICES_H
+#define GetCurrentProcess GetCurrentProcess_Mac
+#define GetCurrentThread GetCurrentThread_Mac
+#define LoadResource LoadResource_Mac
+#define EqualRect EqualRect_Mac
+#define FillRect FillRect_Mac
+#define FrameRect FrameRect_Mac
+#define GetCursor GetCursor_Mac
+#define InvertRect InvertRect_Mac
+#define OffsetRect OffsetRect_Mac
+#define PtInRect PtInRect_Mac
+#define SetCursor SetCursor_Mac
+#define SetRect SetRect_Mac
+#define ShowCursor ShowCursor_Mac
+#define UnionRect UnionRect_Mac
+#define Polygon Polygon_Mac
+#include <ApplicationServices/ApplicationServices.h>
+#undef GetCurrentProcess
+#undef GetCurrentThread
+#undef LoadResource
+#undef EqualRect
+#undef FillRect
+#undef FrameRect
+#undef GetCursor
+#undef InvertRect
+#undef OffsetRect
+#undef PtInRect
+#undef SetCursor
+#undef SetRect
+#undef ShowCursor
+#undef UnionRect
+#undef Polygon
+#undef DPRINTF
+#endif
+
+#define COBJMACROS
+#define NONAMELESSUNION
+
+#include <windows.h>
+#include <shlobj.h>
+#include <objidl.h>
+#include <shlguid.h>
+#include <appmgmt.h>
+#include <tlhelp32.h>
+#include <intshcut.h>
+#include <shlwapi.h>
+
+#include "wine/debug.h"
+#include "wine/library.h"
+
+#include "winemenubuilder.h"
+
+
+WINE_DEFAULT_DEBUG_CHANNEL(menubuilder);
+
+static char *mac_desktop_dir = NULL;
+static char *wine_applications_dir = NULL;
+static char *wine_associations_dir = NULL;
+
+static char *strdupA( const char *str )
+{
+ char *ret;
+
+ if (!str) return NULL;
+ if ((ret = HeapAlloc( GetProcessHeap(), 0, strlen(str) + 1 ))) strcpy( ret, str );
+ return ret;
+}
+
+#define ICNS_SLOTS 6
+
+static inline int size_to_slot(int size)
+{
+ switch (size)
+ {
+ case 16: return 0;
+ case 32: return 1;
+ case 48: return 2;
+ case 64: return -2; /* Classic Mode */
+ case 128: return 3;
+ case 256: return 4;
+ case 512: return 5;
+ }
+
+ return -1;
+}
+
+#define CLASSIC_SLOT 3
+
+HRESULT write_bundle_icon(IStream *icoStream, int exeIndex, LPCWSTR icoPathW,
+ const char *destFilename, char *icnsName)
+{
+ ICONDIRENTRY *iconDirEntries = NULL;
+ int numEntries;
+ struct {
+ int index;
+ int maxBits;
+ BOOL scaled;
+ } best[ICNS_SLOTS];
+ int indexes[ICNS_SLOTS];
+ int i;
+ LARGE_INTEGER zero;
+ HRESULT hr;
+
+ hr = read_ico_direntries(icoStream, &iconDirEntries, &numEntries);
+ if (FAILED(hr))
+ goto end;
+ for (i = 0; i < ICNS_SLOTS; i++)
+ {
+ best[i].index = -1;
+ best[i].maxBits = 0;
+ }
+ for (i = 0; i < numEntries; i++)
+ {
+ int slot;
+ int width = iconDirEntries[i].bWidth ? iconDirEntries[i].bWidth : 256;
+ int height = iconDirEntries[i].bHeight ? iconDirEntries[i].bHeight : 256;
+ BOOL scaled = FALSE;
+
+ WINE_TRACE("[%d]: %d x %d @ %d\n", i, width, height, iconDirEntries[i].wBitCount);
+ if (height != width)
+ continue;
+ slot = size_to_slot(width);
+ if (slot == -2)
+ {
+ scaled = TRUE;
+ slot = CLASSIC_SLOT;
+ }
+ else if (slot < 0)
+ continue;
+ if (scaled && best[slot].maxBits && !best[slot].scaled)
+ continue; /* don't replace unscaled with scaled */
+ if (iconDirEntries[i].wBitCount >= best[slot].maxBits || (!scaled && best[slot].scaled))
+ {
+ best[slot].index = i;
+ best[slot].maxBits = iconDirEntries[i].wBitCount;
+ best[slot].scaled = scaled;
+ }
+ }
+ /* remove the scaled icon if a larger unscaled icon exists */
+ if (best[CLASSIC_SLOT].scaled)
+ {
+ for (i = CLASSIC_SLOT+1; i < ICNS_SLOTS; i++)
+ if (best[i].index >= 0 && !best[i].scaled)
+ {
+ best[CLASSIC_SLOT].index = -1;
+ break;
+ }
+ }
+
+ numEntries = 0;
+ for (i = 0; i < ICNS_SLOTS; i++)
+ {
+ if (best[i].index >= 0)
+ {
+ indexes[numEntries] = best[i].index;
+ numEntries++;
+ }
+ }
+
+ zero.QuadPart = 0;
+ hr = IStream_Seek(icoStream, zero, STREAM_SEEK_SET, NULL);
+ if (FAILED(hr))
+ {
+ WINE_WARN("seeking icon stream failed, error 0x%08X\n", hr);
+ goto end;
+ }
+ hr = convert_to_native_icon(icoStream, indexes, numEntries, &CLSID_WICIcnsEncoder,
+ icnsName, icoPathW);
+ if (FAILED(hr))
+ {
+ WINE_WARN("converting %s to %s failed, error 0x%08X\n",
+ wine_dbgstr_w(icoPathW), wine_dbgstr_a(icnsName), hr);
+ goto end;
+ }
+
+end:
+ HeapFree(GetProcessHeap(), 0, iconDirEntries);
+ return hr;
+}
+
+static HRESULT create_icon_identifier(LPCWSTR icoPathW, char **nativeIdentifier)
+{
+ char *str, *p, *q;
+
+ str = wchars_to_utf8_chars(icoPathW);
+ p = strrchr(str, '\\');
+ if (p == NULL)
+ p = str;
+ else
+ {
+ *p = 0;
+ p++;
+ }
+ q = strrchr(p, '.');
+ if (q)
+ *q = 0;
+ *nativeIdentifier = heap_printf("%s.icns", p);
+
+ HeapFree(GetProcessHeap(), 0, str);
+ return S_OK;
+}
+
+HRESULT appbundle_write_icon(IStream *icoStream, int exeIndex, LPCWSTR icoPathW,
+ const char *destFilename, char **nativeIdentifier)
+{
+ if (*nativeIdentifier)
+ return write_bundle_icon(icoStream, exeIndex, icoPathW, destFilename, *nativeIdentifier);
+ return create_icon_identifier(icoPathW, nativeIdentifier);
+}
+
+CFDictionaryRef create_info_plist_dictionary(const char *pathname, const char *linkname, const char *icon)
+{
+ CFMutableDictionaryRef dict = NULL;
+ CFStringRef pathstr;
+ CFStringRef linkstr;
+
+ pathstr = CFStringCreateWithCString(NULL, pathname, CFStringGetSystemEncoding());
+ linkstr = CFStringCreateWithCString(NULL, linkname, CFStringGetSystemEncoding());
+ if (!pathstr || !linkstr)
+ goto cleanup;
+
+ /* Create a dictionary that will hold the data. */
+ dict = CFDictionaryCreateMutable( kCFAllocatorDefault,
+ 0,
+ &kCFTypeDictionaryKeyCallBacks,
+ &kCFTypeDictionaryValueCallBacks );
+ if (!dict)
+ goto cleanup;
+
+ /* Put the various items into the dictionary. */
+ /* FIXME - Some values assumed the ought not to be */
+ CFDictionarySetValue( dict, CFSTR("CFBundleDevelopmentRegion"), CFSTR("English") );
+ CFDictionarySetValue( dict, CFSTR("CFBundleExecutable"), CFSTR("winelauncher") );
+ /* FIXME - Avoid identifier if not unique. */
+ /* CFDictionarySetValue( dict, CFSTR("CFBundleIdentifier"), CFSTR("org.winehq.wine") ); */
+ CFDictionarySetValue( dict, CFSTR("CFBundleInfoDictionaryVersion"), CFSTR("6.0") );
+ CFDictionarySetValue( dict, CFSTR("CFBundleName"), linkstr );
+ CFDictionarySetValue( dict, CFSTR("CFBundleDisplayName"), pathstr );
+ CFDictionarySetValue( dict, CFSTR("CFBundlePackageType"), CFSTR("APPL") );
+ CFDictionarySetValue( dict, CFSTR("CFBundleVersion"), CFSTR("1.0") );
+
+ if (icon)
+ {
+ CFStringRef iconstr = CFStringCreateWithCString(NULL, icon, CFStringGetSystemEncoding());
+
+ if (iconstr)
+ {
+ CFDictionarySetValue( dict, CFSTR("CFBundleIconFile"), iconstr );
+ CFRelease(iconstr);
+ }
+ }
+ else
+ {
+ /* Fixme - install a default icon */
+ /* CFDictionarySetValue( dict, CFSTR("CFBundleIconFile"), CFSTR("wine.icns") ); */
+ }
+
+cleanup:
+ if (pathstr)
+ CFRelease(pathstr);
+ if (linkstr)
+ CFRelease(linkstr);
+
+ return dict;
+}
+
+BOOL write_property_list( const char *path, CFPropertyListRef propertyList, CFPropertyListFormat format)
+{
+ CFDataRef data = NULL;
+ CFStringRef pathstr = NULL;
+ CFURLRef fileURL = NULL;
+ CFErrorRef err = NULL;
+ BOOL ret = FALSE;
+ SInt32 errorCode;
+
+ pathstr = CFStringCreateWithCString(NULL, path, CFStringGetSystemEncoding());
+ if (!pathstr)
+ goto cleanup;
+
+ fileURL = CFURLCreateWithFileSystemPath( kCFAllocatorDefault,
+ pathstr,
+ kCFURLPOSIXPathStyle,
+ false );
+ if (!fileURL)
+ goto cleanup;
+
+ data = CFPropertyListCreateData(NULL, propertyList, format, 0, &err);
+ if (!data)
+ goto cleanup;
+
+ ret = CFURLWriteDataAndPropertiesToResource (
+ fileURL,
+ data,
+ NULL,
+ &errorCode);
+
+cleanup:
+ if (pathstr)
+ CFRelease(pathstr);
+ if (fileURL)
+ CFRelease(fileURL);
+ if (data)
+ CFRelease(data);
+ if (err)
+ CFRelease(err);
+
+ return ret;
+}
+
+static BOOL write_info_plist(const char *path_to_bundle_contents, CFPropertyListRef propertyList)
+{
+ char *plist_path;
+ BOOL ret = FALSE;
+
+ plist_path = heap_printf("%s/Info.plist", path_to_bundle_contents);
+ if (!plist_path)
+ return FALSE;
+
+ WINE_TRACE("Creating Bundle Info.plist at %s\n", wine_dbgstr_a(plist_path));
+ ret = write_property_list( plist_path, propertyList, kCFPropertyListXMLFormat_v1_0 );
+
+ HeapFree(GetProcessHeap(), 0, plist_path);
+
+ return ret;
+}
+
+CFDictionaryRef create_strings_dictionary(const char *linkname)
+{
+ CFMutableDictionaryRef dict;
+ CFStringRef linkstr;
+
+ linkstr = CFStringCreateWithCString(NULL, linkname, CFStringGetSystemEncoding());
+ if (!linkstr)
+ return NULL;
+
+ dict = CFDictionaryCreateMutable( kCFAllocatorDefault,
+ 1,
+ &kCFTypeDictionaryKeyCallBacks,
+ &kCFTypeDictionaryValueCallBacks );
+ if (dict)
+ CFDictionarySetValue( dict, CFSTR("CFBundleDisplayName"), linkstr );
+
+ CFRelease(linkstr);
+
+ return dict;
+}
+
+static BOOL generate_plist_strings(const char *path_to_bundle_resources_lang, const char *linkname)
+{
+ char *strings_path;
+ CFPropertyListRef propertyList = NULL;
+ BOOL ret = FALSE;
+
+ strings_path = heap_printf("%s/InfoPlist.strings", path_to_bundle_resources_lang);
+ if (!strings_path)
+ goto cleanup;
+
+ propertyList = create_strings_dictionary(linkname);
+ if (!propertyList)
+ goto cleanup;
+
+ WINE_TRACE("Creating InfoPlist.strings at %s\n", wine_dbgstr_a(strings_path));
+ ret = write_property_list( strings_path, propertyList, kCFPropertyListBinaryFormat_v1_0 );
+
+cleanup:
+ HeapFree(GetProcessHeap(), 0, strings_path);
+ if (propertyList)
+ CFRelease(propertyList);
+
+ return ret;
+}
+
+
+/* inspired by write_desktop_entry() in xdg support code */
+static BOOL generate_bundle_script(const char *path_to_bundle_macos, const char *path,
+ const char *args, const char *workdir, const char *linkname)
+{
+ FILE *file;
+ char *bundle_and_script;
+ const char *libpath;
+
+ bundle_and_script = heap_printf("%s/winelauncher", path_to_bundle_macos);
+
+ WINE_TRACE("Creating Bundle helper script at %s\n", wine_dbgstr_a(bundle_and_script));
+
+ file = fopen(bundle_and_script, "w");
+ if (file == NULL)
+ return FALSE;
+
+ fprintf(file, "#!/bin/sh\n");
+ fprintf(file, "#Helper script for %s\n\n", linkname);
+
+ fprintf(file, "PATH=\"%s\"\nexport PATH\n", getenv("PATH"));
+ libpath = getenv("DYLD_FALLBACK_LIBRARY_PATH");
+ if (libpath)
+ fprintf(file, "DYLD_FALLBACK_LIBRARY_PATH=\"%s\"\nexport DYLD_FALLBACK_LIBRARY_PATH\n", libpath);
+ fprintf(file, "WINEPREFIX=\"%s\"\nexport WINEPREFIX\n\n", wine_get_config_dir());
+
+ if (workdir)
+ fprintf(file, "cd \"%s\"\n", workdir);
+ fprintf(file, "exec sh -c \"exec wine %s %s\"\n\n", path, args);
+
+ fprintf(file, "#EOF\n");
+
+ fclose(file);
+ chmod(bundle_and_script, 0755);
+
+ return TRUE;
+}
+
+/* build out the directory structure for the bundle and then populate */
+BOOL build_app_bundle(const char *unix_link, const char *path, const char *args, const char *workdir, const char *dir, const char *link, const char *linkname, char **icon, CFPropertyListRef *infoplist)
+{
+ BOOL ret = FALSE;
+ char *path_to_bundle, *bundle_name, *path_to_bundle_contents, *path_to_bundle_macos;
+ char *path_to_bundle_resources, *path_to_bundle_resources_lang;
+ static const char resources_lang[] = "English.lproj"; /* FIXME */
+ CFPropertyListRef info;
+
+ WINE_TRACE("bundle name %s\n", wine_dbgstr_a(linkname));
+
+ bundle_name = heap_printf("%s.app", link);
+ path_to_bundle = heap_printf("%s/%s", dir, bundle_name);
+ path_to_bundle_contents = heap_printf("%s/Contents", path_to_bundle);
+ path_to_bundle_macos = heap_printf("%s/MacOS", path_to_bundle_contents);
+ path_to_bundle_resources = heap_printf("%s/Resources", path_to_bundle_contents);
+ path_to_bundle_resources_lang = heap_printf("%s/%s", path_to_bundle_resources, resources_lang);
+
+ create_directories(path_to_bundle);
+ create_directories(path_to_bundle_contents);
+ create_directories(path_to_bundle_macos);
+ create_directories(path_to_bundle_resources);
+ create_directories(path_to_bundle_resources_lang);
+
+ WINE_TRACE("created bundle %s\n", wine_dbgstr_a(path_to_bundle));
+
+ ret = generate_bundle_script(path_to_bundle_macos, path, args, workdir, linkname);
+ if(ret==FALSE)
+ return ret;
+
+ info = create_info_plist_dictionary(link, linkname, *icon);
+ if (infoplist)
+ *infoplist = info;
+ else
+ {
+ ret = write_info_plist(path_to_bundle_contents, info);
+ CFRelease(info);
+ if(ret==FALSE)
+ return ret;
+ }
+
+ ret = generate_plist_strings(path_to_bundle_resources_lang, linkname);
+ if (ret == FALSE)
+ return ret;
+
+ if (unix_link)
+ {
+ DWORD ret = register_menus_entry(path_to_bundle, unix_link);
+ if (ret != ERROR_SUCCESS)
+ return FALSE;
+ }
+
+ if (*icon)
+ {
+ char *tmp = heap_printf("%s/%s", path_to_bundle_resources, *icon);
+ HeapFree(GetProcessHeap(), 0, *icon);
+ *icon = tmp;
+ }
+
+ return TRUE;
+}
+
+BOOL register_bundle(const char *path_to_bundle)
+{
+ CFStringRef pathstr;
+ CFURLRef bundleURL;
+
+ pathstr = CFStringCreateWithCString(NULL, path_to_bundle, CFStringGetSystemEncoding());
+ if (!pathstr)
+ return FALSE;
+
+ bundleURL = CFURLCreateWithFileSystemPath( kCFAllocatorDefault, pathstr, kCFURLPOSIXPathStyle, false );
+ if (!bundleURL)
+ {
+ CFRelease(pathstr);
+ return FALSE;
+ }
+
+ LSRegisterURL(bundleURL, true);
+
+ CFRelease(bundleURL);
+ CFRelease(pathstr);
+
+ return TRUE;
+}
+
+int appbundle_build_desktop_link(const char *unix_link, const char *link, const char *link_name, const char *path,
+ const char *args, const char *descr, const char *workdir, char **icon)
+{
+ return !build_app_bundle(unix_link, path, args, workdir, mac_desktop_dir, link_name, link_name, icon, NULL);
+}
+
+int appbundle_build_menu_link(const char *unix_link, const char *link, const char *link_name, const char *path,
+ const char *args, const char *descr, const char *workdir, char **icon)
+{
+ return !build_app_bundle(unix_link, path, args, workdir, wine_applications_dir, link, link_name, icon, NULL);
+}
+
+void *appbundle_refresh_file_type_associations_init(void)
+{
+ static int ok;
+
+ /* Noop */
+ return &ok;
+}
+
+static CFStringRef find_uti_for_tag(CFStringRef tagClass, const char *tag)
+{
+ CFStringRef uti = NULL;
+ CFStringRef tagStr = CFStringCreateWithCStringNoCopy(NULL, tag, kCFStringEncodingUTF8, kCFAllocatorNull);
+
+ if (tagStr)
+ uti = UTTypeCreatePreferredIdentifierForTag(tagClass, tagStr, NULL);
+
+ /* Discard temporary utis */
+ if (uti && CFStringCompareWithOptions(uti, CFSTR("dyn."), CFRangeMake(0, 4), 0) == kCFCompareEqualTo)
+ {
+ CFRelease(uti);
+ uti = NULL;
+ }
+
+ if (tagStr)
+ CFRelease(tagStr);
+ return uti;
+}
+
+static CFMutableDictionaryRef document_type_dictionary(CFStringRef uti, const char *icon)
+{
+ CFStringRef iconStr = icon ? CFStringCreateWithCString(NULL, icon, kCFStringEncodingUTF8) : NULL;
+ CFMutableDictionaryRef res;
+ CFMutableArrayRef utis;
+
+ res = CFDictionaryCreateMutable(NULL, 5, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
+ if (!res)
+ goto cleanup;
+
+ if (iconStr)
+ CFDictionarySetValue(res, CFSTR("CFBundleTypeIconFile"), iconStr);
+
+ CFDictionarySetValue(res, CFSTR("CFBundleTypeName"), uti);
+
+ /* XXX Viewer? Shell? */
+ CFDictionarySetValue(res, CFSTR("CFBundleTypeRole"), CFSTR("Editor"));
+ utis = CFArrayCreateMutable(NULL, 1, &kCFTypeArrayCallBacks);
+ if (!utis)
+ {
+ CFRelease(res);
+ res = NULL;
+ goto cleanup;
+ }
+ CFArrayAppendValue(utis, uti);
+ CFDictionarySetValue(res, CFSTR("LSItemContentTypes"), utis);
+ CFRelease(utis);
+ CFDictionarySetValue(res, CFSTR("LSHandlerRank"), CFSTR("Alternate"));
+
+cleanup:
+ if (iconStr)
+ CFRelease(iconStr);
+
+ return res;
+}
+
+BOOL replace_document_type(CFPropertyListRef propertyList, CFStringRef uti, CFDictionaryRef dict)
+{
+ CFMutableArrayRef docs = (CFMutableArrayRef)CFDictionaryGetValue(propertyList, CFSTR("CFBundleDocumentTypes"));
+ CFIndex count;
+ CFIndex i;
+
+ if (docs)
+ count = CFArrayGetCount(docs);
+ else
+ {
+ docs = CFArrayCreateMutable(NULL, 1, &kCFTypeArrayCallBacks);
+ if (!docs)
+ return FALSE;
+ count = 0;
+ CFDictionarySetValue((CFMutableDictionaryRef)propertyList, CFSTR("CFBundleDocumentTypes"), docs);
+ }
+
+ for ( i = 0 ; i < count ; i++)
+ {
+ CFDictionaryRef d = CFArrayGetValueAtIndex(docs, i);
+ CFStringRef itemUti = CFDictionaryGetValue(d, CFSTR("CFBundleTypeName"));
+
+ if (CFEqual(uti, itemUti))
+ break;
+ }
+
+ if (i < count)
+ {
+ if (dict)
+ {
+ const void *values[] = { dict };
+
+ CFArrayReplaceValues(docs, CFRangeMake(i, 1), values, 1);
+ }
+ else
+ CFArrayRemoveValueAtIndex(docs, i);
+ }
+ else if (docs)
+ CFArrayAppendValue(docs, dict);
+ return TRUE;
+}
+
+
+static CFMutableDictionaryRef exported_uti_dictionary(CFStringRef uti, const char *description, const char *icon,
+ const char *extension, const char *mime_type)
+{
+ CFStringRef descStr = description ? CFStringCreateWithCString(NULL, description, kCFStringEncodingUTF8) : NULL;
+ CFStringRef iconStr = icon ? CFStringCreateWithCString(NULL, icon, kCFStringEncodingUTF8) : NULL;
+ CFStringRef extStr = CFStringCreateWithCString(NULL, extension, kCFStringEncodingUTF8);
+ CFStringRef mimeStr = CFStringCreateWithCString(NULL, mime_type, kCFStringEncodingUTF8);
+ CFDictionaryRef utidict = UTTypeCopyDeclaration(uti);
+ CFMutableDictionaryRef res = NULL;
+ CFDictionaryRef tagdict;
+ const void *tagkeys[2];
+ const void *tagvalues[2];
+
+ if (!extStr || !mimeStr)
+ goto cleanup;
+
+ if (utidict)
+ {
+ res = CFDictionaryCreateMutableCopy(NULL, 5, utidict);
+ CFRelease(utidict);
+ CFDictionaryRemoveValue(res, kUTTypeReferenceURLKey);
+ }
+ else
+ {
+ const void *conformsToStrings[2];
+ CFArrayRef conformsTo;
+
+ /* Have to create from scratch. */
+ res = CFDictionaryCreateMutable(NULL, 5, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
+ if (!res)
+ goto cleanup;
+
+ CFDictionarySetValue(res, kUTTypeIdentifierKey, uti);
+
+ if (strncmp(mime_type, "image/", 6) == 0)
+ conformsToStrings[0] = CFSTR("public.image");
+ else if (strncmp(mime_type, "text/", 5) == 0)
+ conformsToStrings[0] = CFSTR("public.text");
+ else
+ conformsToStrings[0] = CFSTR("public.data");
+ conformsToStrings[1] = CFSTR("public.item");
+ conformsTo = CFArrayCreate(NULL, conformsToStrings, 2, &kCFTypeArrayCallBacks);
+ if (!conformsTo)
+ {
+ CFRelease(res);
+ res = NULL;
+ goto cleanup;
+ }
+ CFDictionarySetValue(res, kUTTypeConformsToKey, conformsTo);
+ CFRelease(conformsTo);
+ }
+
+ if (description)
+ CFDictionarySetValue(res, kUTTypeDescriptionKey, descStr);
+ else
+ CFDictionaryRemoveValue(res, kUTTypeDescriptionKey);
+ if (icon)
+ CFDictionarySetValue(res, kUTTypeIconFileKey, iconStr);
+ else
+ CFDictionaryRemoveValue(res, kUTTypeIconFileKey);
+ tagkeys[0] = CFSTR("public.filename-extension");
+ tagvalues[0] = extStr;
+ tagkeys[1] = CFSTR("public.mime-type");
+ tagvalues[1] = mimeStr;
+ tagdict = CFDictionaryCreate(NULL, tagkeys, tagvalues, 2, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
+ if (!tagdict)
+ {
+ CFRelease(res);
+ res = NULL;
+ goto cleanup;
+ }
+ CFDictionarySetValue(res, kUTTypeTagSpecificationKey, tagdict);
+ CFRelease(tagdict);
+
+cleanup:
+ if (descStr)
+ CFRelease(descStr);
+ if (iconStr)
+ CFRelease(iconStr);
+ if (extStr)
+ CFRelease(extStr);
+ if (mimeStr)
+ CFRelease(mimeStr);
+
+ return res;
+}
+
+BOOL replace_exported_uti(CFPropertyListRef propertyList, CFStringRef uti, CFDictionaryRef dict)
+{
+ CFMutableArrayRef utis = (CFMutableArrayRef)CFDictionaryGetValue(propertyList, kUTExportedTypeDeclarationsKey);
+ CFIndex count;
+ CFIndex i;
+
+ if (utis)
+ count = CFArrayGetCount(utis);
+ else
+ {
+ utis = CFArrayCreateMutable(NULL, 1, &kCFTypeArrayCallBacks);
+ count = 0;
+ CFDictionarySetValue((CFMutableDictionaryRef)propertyList, kUTExportedTypeDeclarationsKey, utis);
+ }
+
+ for ( i = 0 ; i < count ; i++)
+ {
+ CFDictionaryRef d = CFArrayGetValueAtIndex(utis, i);
+ CFStringRef itemUti = CFDictionaryGetValue(d, kUTTypeIdentifierKey);
+
+ if (CFEqual(uti, itemUti))
+ break;
+ }
+
+ if (i < count)
+ {
+ if (dict)
+ CFArrayReplaceValues(utis, CFRangeMake(i, 1), (const void*[]){ dict }, 1);
+ else
+ CFArrayRemoveValueAtIndex(utis, i);
+ }
+ else if (dict)
+ CFArrayAppendValue(utis, dict);
+ return TRUE;
+}
+
+BOOL appbundle_mime_type_for_extension(void *user, const char *extensionA, LPCWSTR extensionW, char **mime_type)
+{
+ CFStringRef uti = NULL;
+ CFStringRef mime = NULL;
+
+ uti = find_uti_for_tag(kUTTagClassFilenameExtension, &extensionA[1]);
+ if (uti)
+ mime = UTTypeCopyPreferredTagWithClass(uti, kUTTagClassMIMEType);
+
+ if (mime) {
+ char buf[1024];
+
+ if (CFStringGetCString(mime, buf, sizeof(buf), kCFStringEncodingUTF8)) {
+ *mime_type = strdupA(buf);
+ }
+ }
+
+ if (uti)
+ CFRelease(uti);
+ if (mime)
+ CFRelease(mime);
+ return TRUE;
+}
+
+BOOL appbundle_write_mime_type_entry(void *user, const char *extensionA, const char *mimeTypeA, const char *friendlyDocNameA)
+{
+ /* Noop, mime types are written as part of the association bundle */
+ return TRUE;
+}
+
+BOOL appbundle_write_association_entry(void *user, const char *extensionA, const char *friendlyAppNameA,
+ const char *friendlyDocNameA, const char *mimeTypeA, const char *progIdA,
+ char **appIconA, char **docIconA)
+{
+ char *bundle_name;
+ CFMutableDictionaryRef dict;
+ CFPropertyListRef propertyList;
+ CFStringRef uti;
+ char utibuf[256];
+ CFStringRef winePrefix = CFStringCreateWithCString(NULL, wine_get_config_dir(), CFStringGetSystemEncoding());
+ CFStringRef progIdStr = CFStringCreateWithCString(NULL, progIdA, CFStringGetSystemEncoding());
+ char *args = NULL;
+ char *path_to_bundle = NULL;
+ char *path_to_bundle_contents = NULL;
+ BOOL ret = FALSE;
+
+ WINE_TRACE("enter extensionA = %s friendlyAppNameA = %s friendlyDocNameA = %s mimeTypeA = %s progIdA = %s appIconA = %s docIconA = %s\n", wine_dbgstr_a(extensionA), wine_dbgstr_a(friendlyAppNameA), wine_dbgstr_a(friendlyDocNameA), wine_dbgstr_a(mimeTypeA), wine_dbgstr_a(progIdA), wine_dbgstr_a(*appIconA), wine_dbgstr_a(*docIconA));
+
+ bundle_name = heap_printf("wine-extension-%s", &extensionA[1]);
+ if (!bundle_name)
+ goto cleanup;
+ path_to_bundle = heap_printf("%s/%s.app", wine_associations_dir, bundle_name);
+ if (!path_to_bundle)
+ goto cleanup;
+ path_to_bundle_contents = heap_printf("%s/Contents", path_to_bundle);
+ if (!path_to_bundle_contents)
+ goto cleanup;
+
+ args = heap_printf("/AppleEvent /ProgIDOpen %s", progIdA);
+ if (!args)
+ goto cleanup;
+
+ WINE_TRACE("new association bundle %s\n", path_to_bundle);
+ ret = build_app_bundle(NULL, "start", args, NULL, wine_associations_dir, bundle_name, friendlyAppNameA, appIconA, &propertyList);
+ if (!ret)
+ goto cleanup;
+
+ CFDictionaryAddValue((CFMutableDictionaryRef)propertyList, CFSTR("org.winehq.wineprefix"), winePrefix);
+ CFDictionaryAddValue((CFMutableDictionaryRef)propertyList, CFSTR("org.winehq.progid"), progIdStr);
+
+ uti = find_uti_for_tag(kUTTagClassMIMEType, mimeTypeA);
+ if (!uti)
+ uti = find_uti_for_tag(kUTTagClassFilenameExtension, &extensionA[1]);
+ if (!uti)
+ uti = CFStringCreateWithFormat(NULL, NULL, CFSTR("org.winehq.extension%s"), extensionA);
+ if (!uti)
+ goto cleanup;
+
+ CFStringGetCString(uti, utibuf, sizeof(utibuf), kCFStringEncodingUTF8);
+ WINE_TRACE("uti = %s\n", utibuf);
+
+ dict = exported_uti_dictionary(uti, friendlyDocNameA, *docIconA, &extensionA[1], mimeTypeA);
+ if (dict)
+ {
+ ret = replace_exported_uti(propertyList, uti, dict);
+ CFRelease(dict);
+ }
+ if (!ret)
+ goto cleanup;
+
+ dict = document_type_dictionary(uti, *docIconA);
+ if (dict)
+ {
+ ret = replace_document_type(propertyList, uti, dict);
+ CFRelease(dict);
+ }
+ if (!ret)
+ goto cleanup;
+
+ ret = write_info_plist(path_to_bundle_contents, propertyList);
+ if (!ret)
+ goto cleanup;
+
+ /* Update docIcon to full path. App icon is handled by build_app_bundle */
+ if (*docIconA)
+ {
+ char *tmp = heap_printf("%s/Resources/%s", path_to_bundle_contents, *docIconA);
+ HeapFree(GetProcessHeap(), 0, *docIconA);
+ *docIconA = tmp;
+ }
+
+ register_bundle(path_to_bundle);
+
+cleanup:
+ if (!ret && path_to_bundle)
+ remove_unix_link(path_to_bundle);
+
+ HeapFree(GetProcessHeap(), 0, path_to_bundle);
+ HeapFree(GetProcessHeap(), 0, path_to_bundle_contents);
+ HeapFree(GetProcessHeap(), 0, args);
+
+ WINE_TRACE("exit %s appIconA = %s docIconA = %s\n", ret ? "TRUE" : "FALSE", wine_dbgstr_a(*appIconA), wine_dbgstr_a(*docIconA));
+
+ return ret;
+}
+
+BOOL appbundle_remove_file_type_association(void *user, const char *extensionA, LPCWSTR extensionW)
+{
+ char *path_to_bundle = heap_printf("%s/wine-extension-%s.app", wine_associations_dir, &extensionA[1]);
+
+ if (path_to_bundle)
+ {
+ WINE_TRACE("removing file type association %s for %s\n", wine_dbgstr_a(path_to_bundle), wine_dbgstr_w(extensionW));
+ if (!remove_unix_link(path_to_bundle))
+ {
+ WINE_ERR("Failed to remove %s: %s\n", path_to_bundle, strerror(errno));
+ return FALSE;
+ }
+ HeapFree(GetProcessHeap(), 0, path_to_bundle);
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+void appbundle_refresh_file_type_associations_cleanup(void *user, BOOL hasChanged)
+{
+}
+
+BOOL appbundle_init(void)
+{
+ WCHAR shellDesktopPath[MAX_PATH];
+
+ HRESULT hr = SHGetFolderPathW(NULL, CSIDL_DESKTOP, NULL, SHGFP_TYPE_CURRENT, shellDesktopPath);
+ if (SUCCEEDED(hr))
+ mac_desktop_dir = wine_get_unix_file_name(shellDesktopPath);
+
+ if (mac_desktop_dir == NULL)
+ {
+ WINE_ERR("error looking up the desktop directory\n");
+ return FALSE;
+ }
+
+ if (getenv("WINE_APPLICATIONS_DIR"))
+ wine_applications_dir = strdupA(getenv("WINE_APPLICATIONS_DIR"));
+ else
+ wine_applications_dir = heap_printf("%s/Applications/Wine", getenv("HOME"));
+ if (!wine_applications_dir)
+ return FALSE;
+
+ if (getenv("WINE_ASSOCIATIONS_DIR"))
+ wine_associations_dir = strdupA(getenv("WINE_ASSOCIATIONS_DIR"));
+ else
+ wine_associations_dir = heap_printf("%s/Library/Wine/Associations", getenv("HOME"));
+ if (!wine_associations_dir)
+ return FALSE;
+
+ create_directories(wine_applications_dir);
+ WINE_TRACE("Applications in %s\n", wine_applications_dir);
+ create_directories(wine_associations_dir);
+ WINE_TRACE("Associations in %s\n", wine_associations_dir);
+
+ return TRUE;
+}
+
+const struct winemenubuilder_dispatch appbundle_dispatch =
+{
+ appbundle_init,
+
+ appbundle_build_desktop_link,
+ appbundle_build_menu_link,
+
+ appbundle_write_icon,
+
+ appbundle_refresh_file_type_associations_init,
+ appbundle_mime_type_for_extension,
+ appbundle_write_mime_type_entry,
+ appbundle_write_association_entry,
+ appbundle_remove_file_type_association,
+ appbundle_refresh_file_type_associations_cleanup
+};
+
+#endif
diff --git a/programs/winemenubuilder/tests/Makefile.in b/programs/winemenubuilder/tests/Makefile.in
index 1fed9f1..764bcf1 100644
--- a/programs/winemenubuilder/tests/Makefile.in
+++ b/programs/winemenubuilder/tests/Makefile.in
@@ -2,6 +2,7 @@ TESTDLL = winemenubuilder.exe
IMPORTS = advapi32 shell32 ole32 psapi
C_SRCS = \
+ appbundle.c \
xdg.c
@MAKE_TEST_RULES@
diff --git a/programs/winemenubuilder/tests/appbundle.c b/programs/winemenubuilder/tests/appbundle.c
new file mode 100644
index 0000000..10d341c
--- /dev/null
+++ b/programs/winemenubuilder/tests/appbundle.c
@@ -0,0 +1,244 @@
+/*
+ * Copyright 2012 Per Johansson
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+ *
+ * TODO
+ * Test the wait flag, complicated since if we start the process it will
+ * wait for us to exit.
+ * Test thumbnail_lnk (-t flag).
+ * Test files contents (might be overkill).
+ */
+
+#define COBJMACROS
+#define NONAMELESSUNION
+
+#include <windows.h>
+#include <shlobj.h>
+#include <shobjidl.h>
+#include <psapi.h>
+#include <intshcut.h>
+
+#include <stdio.h>
+
+#include "wine/test.h"
+#include "winemenubuilder_test.h"
+
+#ifdef __APPLE__
+static void setup_environment(void)
+{
+ HKEY key;
+
+ putenv((char*)"WINE_APPLICATIONS_DIR=apps");
+ putenv((char*)"WINE_ASSOCIATIONS_DIR=assocs");
+
+ if (!RegOpenKeyA(HKEY_CURRENT_USER, "Software\\Wine\\MenuBuilder", &key))
+ delete_key(key);
+ ok(!RegCreateKeyA(HKEY_CURRENT_USER, "Software\\Wine\\MenuBuilder", &key), "Failed to create dispatch key\n");
+ ok(!RegSetValueExA(key, "Dispatch", 0, REG_SZ, (BYTE*)"appbundle", sizeof("appbundle")), "Failed to set dispatch value\n");
+ RegCloseKey(key);
+}
+
+static void cleanup_environment(void)
+{
+ char buffer[MAX_PATH];
+ HKEY key;
+
+ strcpy(buffer, "apps");
+ ok(remove_recursive(buffer), "Failed to cleanup apps\n");
+ strcpy(buffer, "assocs");
+ ok(remove_recursive(buffer), "Failed to cleanup assocs\n");
+
+ /* TODO: restore old contents, if any */
+ if (!RegOpenKeyA(HKEY_CURRENT_USER, "Software\\Wine\\MenuBuilder", &key))
+ delete_key(key);
+}
+
+static void test_create_association(void)
+{
+ setup_association_keys();
+
+ ok(run_winemenubuilder("-a"), "Failed to run winemenubuilder\n");
+
+ verify_file_present("assocs\\wine-extension-wmbtest2.app\\Contents\\Info.plist");
+ verify_file_present("assocs\\wine-extension-wmbtest2.app\\Contents\\MacOS\\winelauncher");
+ verify_file_present("assocs\\wine-extension-wmbtest2.app\\Contents\\Resources\\cmd.icns");
+ verify_file_present("assocs\\wine-extension-wmbtest2.app\\Contents\\Resources\\English.lproj\\InfoPlist.strings");
+}
+
+static void test_remove_association(void)
+{
+ remove_association_keys();
+
+ ok(run_winemenubuilder("-a"), "Failed to run winemenubuilder\n");
+
+ verify_file_not_present("assocs\\wine-extension-wmbtest2.app");
+}
+
+static void test_link_desktop_program(void)
+{
+ WCHAR path[MAX_PATH];
+ static const WCHAR link[] = {'\\','w','m','b','t','e','s','t','.','l','n','k','\0'};
+ char pathA[MAX_PATH];
+
+ get_common_desktop_directory(path, MAX_PATH);
+ ok(lstrlenW(path) + lstrlenW(link) < MAX_PATH, "buffer overflow\n");
+ lstrcatW(path, link);
+
+ create_link(path, "cmd.exe", NULL, NULL, NULL, NULL, 0);
+
+ wait_for_menubuilder();
+
+ get_private_desktop_directoryA(pathA, MAX_PATH);
+ strcat(pathA, "\\wmbtest.app\\Contents\\MacOS\\winelauncher");
+ verify_file_present(pathA);
+}
+
+static void test_link_menu_program(void)
+{
+ WCHAR path[MAX_PATH];
+ static const WCHAR link[] = {'\\','w','m','b','t','e','s','t','.','l','n','k','\0'};
+
+ get_common_start_menu_directory(path, MAX_PATH);
+ ok(lstrlenW(path) + lstrlenW(link) < MAX_PATH, "buffer overflow\n");
+ lstrcatW(path, link);
+
+ create_link(path, "cmd.exe", "/c exit 0", "C:\\", "WMB test link", "cmd.exe", 0);
+
+ wait_for_menubuilder();
+
+ verify_file_present("apps\\wmbtest.app\\Contents\\Info.plist");
+ verify_file_present("apps\\wmbtest.app\\Contents\\MacOS\\winelauncher");
+ verify_file_present("apps\\wmbtest.app\\Contents\\Resources\\cmd.icns");
+ verify_file_present("apps\\wmbtest.app\\Contents\\Resources\\English.lproj\\InfoPlist.strings");
+}
+
+static void test_link_desktop_url(void)
+{
+ WCHAR path[MAX_PATH];
+ static const WCHAR link[] = {'\\','w','m','b','t','e','s','t','u','r','l','.','u','r','l','\0'};
+ char pathA[MAX_PATH];
+
+ get_common_desktop_directory(path, MAX_PATH);
+ ok(lstrlenW(path) + lstrlenW(link) < MAX_PATH, "buffer overflow\n");
+ lstrcatW(path, link);
+
+ create_url(path, "file:///", NULL, 0);
+
+ wait_for_menubuilder();
+
+ get_private_desktop_directoryA(pathA, MAX_PATH);
+ strcat(pathA, "\\wmbtesturl.app\\Contents\\MacOS\\winelauncher");
+ verify_file_present(pathA);
+}
+
+static void test_link_menu_url(void)
+{
+ WCHAR path[MAX_PATH];
+ static const WCHAR link[] = {'\\','w','m','b','t','e','s','t','u','r','l','.','u','r','l','\0'};
+ static WCHAR cmdExeW[] = {'c','m','d','.','e','x','e','\0'};
+
+ get_common_start_menu_directory(path, MAX_PATH);
+ ok(lstrlenW(path) + lstrlenW(link) < MAX_PATH, "buffer overflow\n");
+ lstrcatW(path, link);
+
+ create_url(path, "file:///", cmdExeW, 0);
+
+ wait_for_menubuilder();
+
+ verify_file_present("apps\\wmbtesturl.app\\Contents\\Info.plist");
+ verify_file_present("apps\\wmbtesturl.app\\Contents\\MacOS\\winelauncher");
+ verify_file_present("apps\\wmbtesturl.app\\Contents\\Resources\\cmd.icns");
+ verify_file_present("apps\\wmbtesturl.app\\Contents\\Resources\\English.lproj\\InfoPlist.strings");
+}
+
+static void test_cleanup(void)
+{
+ WCHAR path[MAX_PATH];
+ static const WCHAR lnk_link[] = {'\\','w','m','b','t','e','s','t','.','l','n','k','\0'};
+ static const WCHAR url_link[] = {'\\','w','m','b','t','e','s','t','u','r','l','.','u','r','l','\0'};
+ char pathA[MAX_PATH];
+
+ get_common_desktop_directory(path, MAX_PATH);
+ lstrcatW(path, lnk_link);
+ DeleteFileW(path);
+
+ get_common_start_menu_directory(path, MAX_PATH);
+ lstrcatW(path, lnk_link);
+ DeleteFileW(path);
+
+ get_common_desktop_directory(path, MAX_PATH);
+ lstrcatW(path, url_link);
+ DeleteFileW(path);
+
+ get_common_start_menu_directory(path, MAX_PATH);
+ lstrcatW(path, url_link);
+ DeleteFileW(path);
+
+ ok(run_winemenubuilder("-r"), "Failed to run winemenubuilder\n");
+
+ verify_file_not_present("apps\\wmbtest.app");
+
+ get_private_desktop_directoryA(pathA, MAX_PATH);
+ strcat(pathA, "\\wmbtest.app");
+ verify_file_not_present(pathA);
+
+ verify_file_not_present("apps\\wmbtesturl.app");
+
+ get_private_desktop_directoryA(pathA, MAX_PATH);
+ strcat(pathA, "\\wmbtesturl.app");
+ /* winemenubuilder does not track URLs on the desktop for some reason. */
+#if 0
+ verify_file_not_present(pathA);
+#else
+ remove_recursive(pathA);
+#endif
+}
+#endif /*__APPLE__*/
+
+START_TEST(appbundle)
+{
+#ifdef __APPLE__
+ HRESULT r;
+
+ if (!winemenubuilder_available()) {
+ win_skip("winemenubuilder not installed, skipping tests\n");
+ return;
+ }
+
+ r = CoInitialize(NULL);
+ ok(r == S_OK, "CoInitialize failed (0x%08x)\n", r);
+ if (r != S_OK)
+ return;
+
+ wait_for_boot();
+ wait_for_menubuilder();
+
+ setup_environment();
+
+ test_create_association();
+ test_remove_association();
+
+ test_link_desktop_program();
+ test_link_menu_program();
+ test_link_desktop_url();
+ test_link_menu_url();
+
+ test_cleanup();
+
+ cleanup_environment();
+#endif
+}
+
diff --git a/programs/winemenubuilder/tests/winemenubuilder_test.h b/programs/winemenubuilder/tests/winemenubuilder_test.h
new file mode 100644
index 0000000..e186c7f
--- /dev/null
+++ b/programs/winemenubuilder/tests/winemenubuilder_test.h
@@ -0,0 +1,17 @@
+
+DWORD delete_key( HKEY hkey );
+BOOL remove_recursive(char *path);
+BOOL winemenubuilder_available(void);
+BOOL run_winemenubuilder(const char *args);
+void wait_for_boot(void);
+void verify_file_present(const char *path);
+void verify_file_not_present(const char *path);
+void create_link(const WCHAR *link, const char *cmd, const char *args, const char *workdir,
+ const char *desc, const char *iconPath, int iconId);
+void create_url(const WCHAR *link, const char *url, WCHAR *iconPath, int iconId);
+void wait_for_menubuilder(void);
+WCHAR *get_common_desktop_directory(WCHAR *buffer, size_t buflen);
+WCHAR *get_common_start_menu_directory(WCHAR *buffer, size_t buflen);
+char *get_private_desktop_directoryA(char *buffer, size_t buflen);
+void setup_association_keys(void);
+void remove_association_keys(void);
diff --git a/programs/winemenubuilder/tests/xdg.c b/programs/winemenubuilder/tests/xdg.c
index 0619369..22e127e 100644
--- a/programs/winemenubuilder/tests/xdg.c
+++ b/programs/winemenubuilder/tests/xdg.c
@@ -35,6 +35,7 @@
#include <stdio.h>
#include "wine/test.h"
+#include "winemenubuilder_test.h"
/* delete key and all its subkeys */
DWORD delete_key( HKEY hkey )
@@ -467,17 +468,13 @@ static void test_create_association(void)
verify_file_present("xdg_data\\mime\\packages\\x-wine-extension-wmbtest1.xml");
verify_file_present("xdg_data\\mime\\packages\\x-wine-extension-wmbtest2.xml");
-#ifndef __APPLE__ /* Icons go to /tmp/ on OS X */
verify_file_present("xdg_data\\icons\\hicolor\\16x16\\apps\\application-x-wine-extension-wmbtest2.png");
verify_file_present("xdg_data\\icons\\hicolor\\32x32\\apps\\application-x-wine-extension-wmbtest2.png");
verify_file_present("xdg_data\\icons\\hicolor\\48x48\\apps\\application-x-wine-extension-wmbtest2.png");
-#endif
verify_file_present("xdg_data\\applications\\wine-extension-wmbtest2.desktop");
-#ifndef __APPLE__
verify_file_present("xdg_data\\icons\\hicolor\\16x16\\apps\\*_cmd.0.png");
verify_file_present("xdg_data\\icons\\hicolor\\32x32\\apps\\*_cmd.0.png");
verify_file_present("xdg_data\\icons\\hicolor\\48x48\\apps\\*_cmd.0.png");
-#endif
}
static void test_remove_association(void)
diff --git a/programs/winemenubuilder/winemenubuilder.c b/programs/winemenubuilder/winemenubuilder.c
index 8af388b..08df362 100644
--- a/programs/winemenubuilder/winemenubuilder.c
+++ b/programs/winemenubuilder/winemenubuilder.c
@@ -77,6 +77,8 @@
#ifdef HAVE_FNMATCH_H
#include <fnmatch.h>
#endif
+#include <dirent.h>
+#include <limits.h>
#define COBJMACROS
#define NONAMELESSUNION
@@ -165,8 +167,6 @@ struct rb_string_entry
struct wine_rb_entry entry;
};
-DEFINE_GUID(CLSID_WICIcnsEncoder, 0x312fb6f1,0xb767,0x409d,0x8a,0x6d,0x0f,0xc1,0x54,0xd4,0xf0,0x5c);
-
const struct winemenubuilder_dispatch *wmb_dispatch;
static HRESULT open_icon(LPCWSTR filename, int index, BOOL bWait, IStream **ppStream);
@@ -1000,162 +1000,14 @@ static HRESULT open_icon(LPCWSTR filename, int index, BOOL bWait, IStream **ppSt
return hr;
}
-#if 0
-#define ICNS_SLOTS 6
-
-static inline int size_to_slot(int size)
-{
- switch (size)
- {
- case 16: return 0;
- case 32: return 1;
- case 48: return 2;
- case 64: return -2; /* Classic Mode */
- case 128: return 3;
- case 256: return 4;
- case 512: return 5;
- }
-
- return -1;
-}
-
-#define CLASSIC_SLOT 3
-
-static HRESULT platform_write_icon(IStream *icoStream, int exeIndex, LPCWSTR icoPathW,
- const char *destFilename, char **nativeIdentifier)
-{
- ICONDIRENTRY *iconDirEntries = NULL;
- int numEntries;
- struct {
- int index;
- int maxBits;
- BOOL scaled;
- } best[ICNS_SLOTS];
- int indexes[ICNS_SLOTS];
- int i;
- GUID guid;
- WCHAR *guidStrW = NULL;
- char *guidStrA = NULL;
- char *icnsPath = NULL;
- LARGE_INTEGER zero;
- HRESULT hr;
-
- hr = read_ico_direntries(icoStream, &iconDirEntries, &numEntries);
- if (FAILED(hr))
- goto end;
- for (i = 0; i < ICNS_SLOTS; i++)
- {
- best[i].index = -1;
- best[i].maxBits = 0;
- }
- for (i = 0; i < numEntries; i++)
- {
- int slot;
- int width = iconDirEntries[i].bWidth ? iconDirEntries[i].bWidth : 256;
- int height = iconDirEntries[i].bHeight ? iconDirEntries[i].bHeight : 256;
- BOOL scaled = FALSE;
-
- WINE_TRACE("[%d]: %d x %d @ %d\n", i, width, height, iconDirEntries[i].wBitCount);
- if (height != width)
- continue;
- slot = size_to_slot(width);
- if (slot == -2)
- {
- scaled = TRUE;
- slot = CLASSIC_SLOT;
- }
- else if (slot < 0)
- continue;
- if (scaled && best[slot].maxBits && !best[slot].scaled)
- continue; /* don't replace unscaled with scaled */
- if (iconDirEntries[i].wBitCount >= best[slot].maxBits || (!scaled && best[slot].scaled))
- {
- best[slot].index = i;
- best[slot].maxBits = iconDirEntries[i].wBitCount;
- best[slot].scaled = scaled;
- }
- }
- /* remove the scaled icon if a larger unscaled icon exists */
- if (best[CLASSIC_SLOT].scaled)
- {
- for (i = CLASSIC_SLOT+1; i < ICNS_SLOTS; i++)
- if (best[i].index >= 0 && !best[i].scaled)
- {
- best[CLASSIC_SLOT].index = -1;
- break;
- }
- }
-
- numEntries = 0;
- for (i = 0; i < ICNS_SLOTS; i++)
- {
- if (best[i].index >= 0)
- {
- indexes[numEntries] = best[i].index;
- numEntries++;
- }
- }
-
- hr = CoCreateGuid(&guid);
- if (FAILED(hr))
- {
- WINE_WARN("CoCreateGuid failed, error 0x%08X\n", hr);
- goto end;
- }
- hr = StringFromCLSID(&guid, &guidStrW);
- if (FAILED(hr))
- {
- WINE_WARN("StringFromCLSID failed, error 0x%08X\n", hr);
- goto end;
- }
- guidStrA = wchars_to_utf8_chars(guidStrW);
- if (guidStrA == NULL)
- {
- hr = E_OUTOFMEMORY;
- WINE_WARN("out of memory converting GUID string\n");
- goto end;
- }
- icnsPath = heap_printf("/tmp/%s.icns", guidStrA);
- if (icnsPath == NULL)
- {
- hr = E_OUTOFMEMORY;
- WINE_WARN("out of memory creating ICNS path\n");
- goto end;
- }
- zero.QuadPart = 0;
- hr = IStream_Seek(icoStream, zero, STREAM_SEEK_SET, NULL);
- if (FAILED(hr))
- {
- WINE_WARN("seeking icon stream failed, error 0x%08X\n", hr);
- goto end;
- }
- hr = convert_to_native_icon(icoStream, indexes, numEntries, &CLSID_WICIcnsEncoder,
- icnsPath, icoPathW);
- if (FAILED(hr))
- {
- WINE_WARN("converting %s to %s failed, error 0x%08X\n",
- wine_dbgstr_w(icoPathW), wine_dbgstr_a(icnsPath), hr);
- goto end;
- }
-
-end:
- HeapFree(GetProcessHeap(), 0, iconDirEntries);
- CoTaskMemFree(guidStrW);
- HeapFree(GetProcessHeap(), 0, guidStrA);
- if (SUCCEEDED(hr))
- *nativeIdentifier = icnsPath;
- else
- HeapFree(GetProcessHeap(), 0, icnsPath);
- return hr;
-}
-#endif
-
-/* extract an icon from an exe or icon file; helper for IPersistFile_fnSave */
-char *extract_icon(LPCWSTR icoPathW, int index, const char *destFilename, BOOL bWait)
+/* extract an icon from an exe or icon file; helper for IPersistFile_fnSave
+ * This function is called twice, once before generating the main file, once after.
+ * Whatever is written to *nativeIdentifier is kept for the second call.
+ */
+void extract_icon(LPCWSTR icoPathW, int index, const char *destFilename, BOOL bWait, char **nativeIdentifier)
{
IStream *stream = NULL;
HRESULT hr;
- char *nativeIdentifier = NULL;
WINE_TRACE("path=[%s] index=%d destFilename=[%s]\n", wine_dbgstr_w(icoPathW), index, wine_dbgstr_a(destFilename));
@@ -1165,7 +1017,7 @@ char *extract_icon(LPCWSTR icoPathW, int index, const char *destFilename, BOOL b
WINE_WARN("opening icon %s index %d failed, hr=0x%08X\n", wine_dbgstr_w(icoPathW), index, hr);
goto end;
}
- hr = wmb_dispatch->write_icon(stream, index, icoPathW, destFilename, &nativeIdentifier);
+ hr = wmb_dispatch->write_icon(stream, index, icoPathW, destFilename, nativeIdentifier);
if (FAILED(hr))
WINE_WARN("writing icon failed, error 0x%08X\n", hr);
@@ -1174,10 +1026,9 @@ end:
IStream_Release(stream);
if (FAILED(hr))
{
- HeapFree(GetProcessHeap(), 0, nativeIdentifier);
- nativeIdentifier = NULL;
+ HeapFree(GetProcessHeap(), 0, *nativeIdentifier);
+ *nativeIdentifier = NULL;
}
- return nativeIdentifier;
}
static HKEY open_menus_reg_key(void)
@@ -1231,6 +1082,55 @@ DWORD register_menus_entry(const char *unix_file, const char *windows_file)
return ret;
}
+BOOL remove_unix_link(const char *unix_link)
+{
+ struct stat st;
+
+ if (lstat(unix_link, &st))
+ return FALSE;
+
+ if (S_ISDIR(st.st_mode))
+ {
+ char path[PATH_MAX], *epath;
+ DIR *dir = opendir(unix_link);
+ struct dirent *ent;
+
+ if (!dir)
+ return FALSE;
+
+ epath = stpcpy(path, unix_link);
+ if (epath - path >= PATH_MAX - 1)
+ return FALSE;
+ *epath++ = '/';
+
+ while ((ent = readdir(dir)))
+ {
+ if (strcmp(ent->d_name, ".") == 0 || strcmp(ent->d_name, "..") == 0)
+ continue;
+ if ((epath - path) + ent->d_namlen >= PATH_MAX)
+ {
+ closedir(dir);
+ return FALSE;
+ }
+ strcpy(epath, ent->d_name);
+ if (!remove_unix_link(path))
+ {
+ closedir(dir);
+ return FALSE;
+ }
+ }
+ closedir(dir);
+
+ if (rmdir(unix_link))
+ return FALSE;
+ }
+ else
+ {
+ if (unlink(unix_link))
+ return FALSE;
+ }
+ return TRUE;
+}
/* This escapes reserved characters in .desktop files' Exec keys. */
LPSTR escape(LPCWSTR arg)
@@ -1829,6 +1729,7 @@ static BOOL generate_associations(void *user)
WCHAR *commandW = NULL;
WCHAR *executableW = NULL;
char *openWithIconA = NULL;
+ char *regOpenWithIconA = NULL;
WCHAR *friendlyDocNameW = NULL;
char *friendlyDocNameA = NULL;
WCHAR *iconW = NULL;
@@ -1840,6 +1741,8 @@ static BOOL generate_associations(void *user)
WCHAR *progIdW = NULL;
char *progIdA = NULL;
char *mimeProgId = NULL;
+ char *flattened_mime = NULL;
+ int icon_index = 0;
extensionA = wchars_to_utf8_chars(strlwrW(extensionW));
if (extensionA == NULL)
@@ -1884,18 +1787,16 @@ static BOOL generate_associations(void *user)
*/
if (iconW)
{
- char *flattened_mime = slashes_to_minuses(mimeTypeA);
+ flattened_mime = slashes_to_minuses(mimeTypeA);
if (flattened_mime)
{
- int index = 0;
WCHAR *comma = strrchrW(iconW, ',');
if (comma)
{
*comma = 0;
- index = atoiW(comma + 1);
+ icon_index = atoiW(comma + 1);
}
- iconA = extract_icon(iconW, index, flattened_mime, FALSE);
- HeapFree(GetProcessHeap(), 0, flattened_mime);
+ extract_icon(iconW, icon_index, flattened_mime, FALSE, &iconA);
}
}
@@ -1916,7 +1817,7 @@ static BOOL generate_associations(void *user)
executableW = assoc_query(ASSOCSTR_EXECUTABLE, extensionW, openW);
if (executableW)
- openWithIconA = extract_icon(executableW, 0, NULL, FALSE);
+ extract_icon(executableW, 0, NULL, FALSE, &openWithIconA);
friendlyAppNameW = assoc_query(ASSOCSTR_FRIENDLYAPPNAME, extensionW, openW);
if (friendlyAppNameW)
@@ -1975,12 +1876,20 @@ static BOOL generate_associations(void *user)
}
}
- if (has_association_changed(extensionW, mimeTypeA, progIdW, friendlyAppNameA, openWithIconA))
+ if (openWithIconA)
+ regOpenWithIconA = strdupA(openWithIconA);
+ if (has_association_changed(extensionW, mimeTypeA, progIdW, friendlyAppNameA, regOpenWithIconA))
{
- if (wmb_dispatch->write_association_entry(user, extensionA, friendlyAppNameA, friendlyDocNameA, mimeTypeA, progIdA, openWithIconA))
+ if (wmb_dispatch->write_association_entry(user, extensionA, friendlyAppNameA, friendlyDocNameA, mimeTypeA, progIdA, &openWithIconA, &iconA))
{
hasChanged = TRUE;
- update_association(extensionW, mimeTypeA, progIdW, friendlyAppNameA, openWithIconA);
+ update_association(extensionW, mimeTypeA, progIdW, friendlyAppNameA, regOpenWithIconA);
+
+ /* Second icon pass after files are written. */
+ if (executableW)
+ extract_icon(executableW, 0, NULL, FALSE, &openWithIconA);
+ if (flattened_mime)
+ extract_icon(iconW, icon_index, flattened_mime, FALSE, &iconA);
}
}
@@ -1989,6 +1898,7 @@ static BOOL generate_associations(void *user)
HeapFree(GetProcessHeap(), 0, commandW);
HeapFree(GetProcessHeap(), 0, executableW);
HeapFree(GetProcessHeap(), 0, openWithIconA);
+ HeapFree(GetProcessHeap(), 0, regOpenWithIconA);
HeapFree(GetProcessHeap(), 0, friendlyDocNameW);
HeapFree(GetProcessHeap(), 0, friendlyDocNameA);
HeapFree(GetProcessHeap(), 0, iconW);
@@ -1999,6 +1909,7 @@ static BOOL generate_associations(void *user)
HeapFree(GetProcessHeap(), 0, friendlyAppNameA);
HeapFree(GetProcessHeap(), 0, progIdW);
HeapFree(GetProcessHeap(), 0, progIdA);
+ HeapFree(GetProcessHeap(), 0, flattened_mime);
}
HeapFree(GetProcessHeap(), 0, extensionW);
if (ret != ERROR_SUCCESS)
@@ -2098,11 +2009,11 @@ static BOOL InvokeShellLinker( IShellLinkW *sl, LPCWSTR link, BOOL bWait )
WINE_TRACE("pidl path : %s\n", wine_dbgstr_w(szPath));
}
- /* extract the icon */
+ /* extract the icon, first try */
if( szIconPath[0] )
- icon_name = extract_icon( szIconPath , iIconId, NULL, bWait );
+ extract_icon( szIconPath , iIconId, NULL, bWait, &icon_name );
else
- icon_name = extract_icon( szPath, iIconId, NULL, bWait );
+ extract_icon( szPath, iIconId, NULL, bWait, &icon_name );
/* fail - try once again after parent process exit */
if( !icon_name )
@@ -2204,13 +2115,13 @@ static BOOL InvokeShellLinker( IShellLinkW *sl, LPCWSTR link, BOOL bWait )
if (link_arg)
{
r = wmb_dispatch->build_desktop_link(unix_link, link_name, lastEntry,
- start_path, link_arg, description, work_dir, icon_name);
+ start_path, link_arg, description, work_dir, &icon_name);
HeapFree(GetProcessHeap(), 0, link_arg);
}
}
else
{
- r = wmb_dispatch->build_desktop_link(NULL, link_name, lastEntry, escaped_path, escaped_args, description, work_dir, icon_name);
+ r = wmb_dispatch->build_desktop_link(NULL, link_name, lastEntry, escaped_path, escaped_args, description, work_dir, &icon_name);
}
}
else
@@ -2218,10 +2129,16 @@ static BOOL InvokeShellLinker( IShellLinkW *sl, LPCWSTR link, BOOL bWait )
char *link_arg = escape_unix_link_arg(unix_link);
if (link_arg)
{
- r = wmb_dispatch->build_menu_link(unix_link, link_name, lastEntry, start_path, link_arg, description, work_dir, icon_name);
+ r = wmb_dispatch->build_menu_link(unix_link, link_name, lastEntry, start_path, link_arg, description, work_dir, &icon_name);
HeapFree(GetProcessHeap(), 0, link_arg);
}
}
+ /* extract the icon, second try */
+ if( szIconPath[0] )
+ extract_icon( szIconPath , iIconId, NULL, bWait, &icon_name );
+ else
+ extract_icon( szPath, iIconId, NULL, bWait, &icon_name );
+
ReleaseSemaphore( hsem, 1, NULL );
@@ -2261,6 +2178,9 @@ static BOOL InvokeShellLinkerForURL( IUniformResourceLocatorW *url, LPCWSTR link
BOOL has_icon = FALSE;
char *lastEntry;
+ pv[0].vt = VT_EMPTY;
+ pv[1].vt = VT_EMPTY;
+
if ( !link )
{
WINE_ERR("Link name is null\n");
@@ -2326,12 +2246,10 @@ static BOOL InvokeShellLinkerForURL( IUniformResourceLocatorW *url, LPCWSTR link
if (pv[0].vt == VT_LPWSTR && pv[0].u.pwszVal && pv[0].u.pwszVal[0])
{
has_icon = TRUE;
- icon_name = extract_icon( pv[0].u.pwszVal, pv[1].u.iVal, NULL, bWait );
+ extract_icon( pv[0].u.pwszVal, pv[1].u.iVal, NULL, bWait, &icon_name );
WINE_TRACE("URL icon path: %s icon index: %d icon name: %s\n", wine_dbgstr_w(pv[0].u.pwszVal), pv[1].u.iVal, icon_name);
}
- PropVariantClear(&pv[0]);
- PropVariantClear(&pv[1]);
}
IPropertyStorage_Release(pPropStg);
}
@@ -2363,9 +2281,16 @@ static BOOL InvokeShellLinkerForURL( IUniformResourceLocatorW *url, LPCWSTR link
else
++lastEntry;
if (in_desktop_dir(csidl))
- r = wmb_dispatch->build_desktop_link(NULL, link_name, lastEntry, start_path, escaped_urlPath, NULL, NULL, icon_name);
+ r = wmb_dispatch->build_desktop_link(NULL, link_name, lastEntry, start_path, escaped_urlPath, NULL, NULL, &icon_name);
else
- r = wmb_dispatch->build_menu_link(unix_link, link_name, lastEntry, start_path, escaped_urlPath, NULL, NULL, icon_name);
+ r = wmb_dispatch->build_menu_link(unix_link, link_name, lastEntry, start_path, escaped_urlPath, NULL, NULL, &icon_name);
+
+ if (has_icon)
+ {
+ extract_icon( pv[0].u.pwszVal, pv[1].u.iVal, NULL, bWait, &icon_name );
+ WINE_TRACE("URL icon path: %s icon index: %d icon name: %s\n", wine_dbgstr_w(pv[0].u.pwszVal), pv[1].u.iVal, icon_name);
+ }
+
ret = (r == 0);
ReleaseSemaphore(hSem, 1, NULL);
@@ -2377,6 +2302,8 @@ cleanup:
CoTaskMemFree( urlPath );
HeapFree(GetProcessHeap(), 0, escaped_urlPath);
HeapFree(GetProcessHeap(), 0, unix_link);
+ PropVariantClear(&pv[0]);
+ PropVariantClear(&pv[1]);
return ret;
}
@@ -2622,7 +2549,7 @@ static void cleanup_menus(void)
if (stat(windows_file, &filestats) < 0 && errno == ENOENT)
{
WINE_TRACE("removing menu related file %s\n", unix_file);
- remove(unix_file);
+ remove_unix_link(unix_file);
RegDeleteValueW(hkey, value);
}
else
@@ -2784,6 +2711,9 @@ static WCHAR *next_token( LPWSTR *p )
static BOOL dispatch_init(void)
{
extern struct winemenubuilder_dispatch xdg_dispatch;
+#ifdef __APPLE__
+ extern struct winemenubuilder_dispatch appbundle_dispatch;
+#endif
const char *dispatch = NULL;
unsigned char buffer[256];
HKEY hkey;
@@ -2808,6 +2738,13 @@ static BOOL dispatch_init(void)
if (dispatch)
{
+#ifdef __APPLE__
+ if (strcmp(dispatch, "appbundle") == 0)
+ {
+ wmb_dispatch = &appbundle_dispatch;
+ return TRUE;
+ }
+#endif
if (strcmp(dispatch, "xdg") == 0)
{
wmb_dispatch = &xdg_dispatch;
@@ -2817,8 +2754,13 @@ static BOOL dispatch_init(void)
WINE_WARN("Unknown Wine MenuBuilder Dispatch \"%s\"\n", dispatch);
}
+#ifdef __APPLE__
+ wmb_dispatch = &appbundle_dispatch;
+ WINE_TRACE("Dispatch set to appbundle by default\n");
+#else
wmb_dispatch = &xdg_dispatch;
WINE_TRACE("Dispatch set to xdg by default\n");
+#endif
return TRUE;
}
diff --git a/programs/winemenubuilder/winemenubuilder.h b/programs/winemenubuilder/winemenubuilder.h
index 939b366..b1d0c12 100644
--- a/programs/winemenubuilder/winemenubuilder.h
+++ b/programs/winemenubuilder/winemenubuilder.h
@@ -35,6 +35,8 @@ typedef struct
WORD idCount;
} ICONDIR;
+DEFINE_GUID(CLSID_WICIcnsEncoder, 0x312fb6f1,0xb767,0x409d,0x8a,0x6d,0x0f,0xc1,0x54,0xd4,0xf0,0x5c);
+
char* heap_printf(const char *format, ...);
WCHAR* assoc_query(ASSOCSTR assocStr, LPCWSTR name, LPCWSTR extra);
BOOL create_directories(char *directory);
@@ -46,19 +48,20 @@ char* wchars_to_utf8_chars(LPCWSTR string);
HRESULT convert_to_native_icon(IStream *icoFile, int *indeces, int numIndeces,
const CLSID *outputFormat, const char *outputFileName, LPCWSTR commentW);
-char *extract_icon(LPCWSTR icoPathW, int index, const char *destFilename, BOOL bWait);
+void extract_icon(LPCWSTR icoPathW, int index, const char *destFilename, BOOL bWait, char **nativeIdentifier);
LPSTR escape(LPCWSTR arg);
WCHAR* utf8_chars_to_wchars(LPCSTR string);
+BOOL remove_unix_link(const char *unix_link);
struct winemenubuilder_dispatch
{
BOOL (*init)(void);
int (*build_desktop_link)(const char *unix_link, const char *link, const char *link_name, const char *path,
- const char *args, const char *descr, const char *workdir, char *icon);
+ const char *args, const char *descr, const char *workdir, char **icon);
int (*build_menu_link)(const char *unix_link, const char *link, const char *link_name, const char *path,
- const char *args, const char *descr, const char *workdir, char *icon);
+ const char *args, const char *descr, const char *workdir, char **icon);
HRESULT (*write_icon)(IStream *icoStream, int exeIndex, LPCWSTR icoPathW,
const char *destFilename, char **nativeIdentifier);
@@ -69,7 +72,7 @@ struct winemenubuilder_dispatch
BOOL (*write_mime_type_entry)(void *user, const char *extensionA, const char *mimeTypeA, const char *friendlyDocNameA);
BOOL (*write_association_entry)(void *user, const char *extensionA, const char *friendlyAppNameA,
const char *friendlyDocNameA, const char *mimeTypeA, const char *progIdA,
- char *appIconA);
+ char **appIconA, char **docIconA);
BOOL (*remove_file_type_association)(void *user, const char *extensionA, LPCWSTR extensionW);
void (*refresh_file_type_associations_cleanup)(void *user, BOOL hasChanged);
};
diff --git a/programs/winemenubuilder/xdg.c b/programs/winemenubuilder/xdg.c
index b9490d0..a1bdd95 100644
--- a/programs/winemenubuilder/xdg.c
+++ b/programs/winemenubuilder/xdg.c
@@ -216,6 +216,12 @@ HRESULT xdg_write_icon(IStream *icoStream, int exeIndex, LPCWSTR icoPathW,
HRESULT hr = S_OK;
LARGE_INTEGER zero;
+ if (*nativeIdentifier)
+ {
+ /* Did all work in the first call. */
+ return hr;
+ }
+
hr = read_ico_direntries(icoStream, &iconDirEntries, &numEntries);
if (FAILED(hr))
goto end;
@@ -783,7 +789,7 @@ static BOOL write_freedesktop_mime_type_entry(void *user, const char *dot_extens
static BOOL write_freedesktop_association_entry(void *user, const char *dot_extension,
const char *friendlyAppName, const char *friendlyDocNameA,
const char *mimeType, const char *progId,
- char *openWithIcon)
+ char **openWithIcon, char **docIcon)
{
struct xdg_file_type_user_data *ud = user;
BOOL ret = FALSE;
@@ -797,7 +803,7 @@ static BOOL write_freedesktop_association_entry(void *user, const char *dot_exte
WINE_TRACE("writing association for file type %s, friendlyAppName=%s, MIME type %s, progID=%s, icon=%s to file %s\n",
wine_dbgstr_a(dot_extension), wine_dbgstr_a(friendlyAppName), wine_dbgstr_a(mimeType),
- wine_dbgstr_a(progId), wine_dbgstr_a(openWithIcon), wine_dbgstr_a(desktopPath));
+ wine_dbgstr_a(progId), wine_dbgstr_a(*openWithIcon), wine_dbgstr_a(desktopPath));
desktop = fopen(desktopPath, "w");
if (desktop)
@@ -809,8 +815,8 @@ static BOOL write_freedesktop_association_entry(void *user, const char *dot_exte
fprintf(desktop, "Exec=env WINEPREFIX=\"%s\" wine start /ProgIDOpen %s %%f\n", wine_get_config_dir(), progId);
fprintf(desktop, "NoDisplay=true\n");
fprintf(desktop, "StartupNotify=true\n");
- if (openWithIcon)
- fprintf(desktop, "Icon=%s\n", openWithIcon);
+ if (*openWithIcon)
+ fprintf(desktop, "Icon=%s\n", *openWithIcon);
ret = TRUE;
fclose(desktop);
}
@@ -829,7 +835,7 @@ BOOL xdg_remove_file_type_association(void *user, const char *dot_extension, LPC
if (desktopPath)
{
WINE_TRACE("removing file type association for %s\n", wine_dbgstr_w(extensionW));
- remove(desktopPath);
+ remove_unix_link(desktopPath);
HeapFree(GetProcessHeap(), 0, desktopPath);
return TRUE;
}
@@ -902,7 +908,7 @@ void xdg_refresh_file_type_associations_cleanup(void *user, BOOL hasChanged)
}
int xdg_build_desktop_link(const char *unix_link, const char *link, const char *link_name, const char *path,
- const char *args, const char *descr, const char *workdir, char *icon)
+ const char *args, const char *descr, const char *workdir, char **icon)
{
char *location;
int r = -1;
@@ -911,7 +917,7 @@ int xdg_build_desktop_link(const char *unix_link, const char *link, const char *
if (location)
{
r = !write_desktop_entry(unix_link, location, link_name,
- path, args, descr, workdir, icon);
+ path, args, descr, workdir, *icon);
if (r == 0)
chmod(location, 0755);
}
@@ -919,9 +925,9 @@ int xdg_build_desktop_link(const char *unix_link, const char *link, const char *
}
int xdg_build_menu_link(const char *unix_link, const char *link, const char *link_name, const char *path,
- const char *args, const char *descr, const char *workdir, char *icon)
+ const char *args, const char *descr, const char *workdir, char **icon)
{
- return !write_menu_entry(unix_link, link, link_name, path, args, descr, workdir, icon);
+ return !write_menu_entry(unix_link, link, link_name, path, args, descr, workdir, *icon);
}
--
1.8.0
More information about the wine-patches
mailing list