[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