From 4ec8a03e7a4aad3f6e08d8513a08857a0347e266 Mon Sep 17 00:00:00 2001 From: Per Johansson Date: Sun, 8 Jul 2012 15:22:45 +0200 Subject: [PATCH 3/3] winemenubuilder: Implement OS X application bundles. To: wine-patches Reply-To: wine-devel Icon extraction had to be moved until after main file is created. Make icon extraction into a two step process. File type association excluded due to no way to retrieve the file to open. --- programs/winemenubuilder/Makefile.in | 2 + programs/winemenubuilder/appbundler.c | 590 ++++++++++++++++++++++++++++ programs/winemenubuilder/winemenubuilder.c | 276 ++++++------- programs/winemenubuilder/winemenubuilder.h | 9 +- programs/winemenubuilder/xdg.c | 24 +- 5 filer ändrade, 732 tillägg(+), 169 borttagningar(-) create mode 100644 programs/winemenubuilder/appbundler.c 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..023fc52 --- /dev/null +++ b/programs/winemenubuilder/appbundler.c @@ -0,0 +1,590 @@ +/* + * 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 +#include + +#include +#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 +#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 +#include +#include +#include +#include +#include +#include +#include +#include + +#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; + +DEFINE_GUID(CLSID_WICIcnsEncoder, 0x312fb6f1,0xb767,0x409d,0x8a,0x6d,0x0f,0xc1,0x54,0xd4,0xf0,0x5c); + +#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 128: return 3; + case 256: return 4; + case 512: return 5; + } + + return -1; +} + +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; + } 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; + + 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 < 0) + continue; + if (iconDirEntries[i].wBitCount >= best[slot].maxBits) + { + best[slot].index = i; + best[slot].maxBits = iconDirEntries[i].wBitCount; + } + } + 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"), linkstr ); + /* 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/%s", path_to_bundle_macos, linkname); + + 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) +{ + WINE_FIXME("FileType Associations are currently unsupported on this platform\n"); + return NULL; +} + +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("HOME")) + { + wine_applications_dir = heap_printf("%s/Applications/Wine", getenv("HOME")); + if (!wine_applications_dir) + return FALSE; + create_directories(wine_applications_dir); + WINE_TRACE("Applications in %s\n", wine_applications_dir); + + wine_associations_dir = heap_printf("%s/Library/Wine/Associations", getenv("HOME")); + if (!wine_associations_dir) + return FALSE; + create_directories(wine_associations_dir); + WINE_TRACE("Associations in %s\n", wine_associations_dir); + + return TRUE; + } + else + { + WINE_ERR("No HOME environment variable\n"); + return FALSE; + } +} + +const struct winemenubuilder_dispatch appbundle_dispatch = +{ + appbundle_init, + + appbundle_build_desktop_link, + appbundle_build_menu_link, + + appbundle_write_icon, + + NULL, + NULL, + NULL, + NULL, + NULL, + NULL +}; + +#endif diff --git a/programs/winemenubuilder/winemenubuilder.c b/programs/winemenubuilder/winemenubuilder.c index 6f9203e..feb4e5c 100644 --- a/programs/winemenubuilder/winemenubuilder.c +++ b/programs/winemenubuilder/winemenubuilder.c @@ -77,6 +77,8 @@ #ifdef HAVE_FNMATCH_H #include #endif +#include +#include #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); @@ -973,138 +973,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 128: return 3; - case 256: return 4; - case 512: return 5; - } - - return -1; -} - -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; - } 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; - - 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 < 0) - continue; - if (iconDirEntries[i].wBitCount >= best[slot].maxBits) - { - best[slot].index = i; - best[slot].maxBits = iconDirEntries[i].wBitCount; - } - } - 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)); @@ -1114,7 +990,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); @@ -1123,10 +999,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) @@ -1180,6 +1055,53 @@ 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 = stpncpy(path, unix_link, PATH_MAX); + if (epath - path >= PATH_MAX - 1) + return FALSE; + *epath++ = '/'; + + while ((ent = readdir(dir))) + { + 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) @@ -1778,6 +1700,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; @@ -1789,6 +1712,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) @@ -1833,18 +1758,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); } } @@ -1865,7 +1788,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) @@ -1924,12 +1847,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); } } @@ -1938,6 +1869,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); @@ -1948,6 +1880,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) @@ -2047,11 +1980,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 ) @@ -2153,13 +2086,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 @@ -2167,10 +2100,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 ); @@ -2209,6 +2148,9 @@ static BOOL InvokeShellLinkerForURL( IUniformResourceLocatorW *url, LPCWSTR link char *start_path = NULL; char *lastEntry; + pv[0].vt = VT_EMPTY; + pv[1].vt = VT_EMPTY; + if ( !link ) { WINE_ERR("Link name is null\n"); @@ -2273,12 +2215,10 @@ static BOOL InvokeShellLinkerForURL( IUniformResourceLocatorW *url, LPCWSTR link { if (pv[0].vt == VT_LPWSTR && pv[0].u.pwszVal) { - 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); } @@ -2311,10 +2251,17 @@ static BOOL InvokeShellLinkerForURL( IUniformResourceLocatorW *url, LPCWSTR link ++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 (pv[0].vt == VT_LPWSTR && pv[0].u.pwszVal) + { + 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); @@ -2326,6 +2273,8 @@ cleanup: CoTaskMemFree( urlPath ); HeapFree(GetProcessHeap(), 0, escaped_urlPath); HeapFree(GetProcessHeap(), 0, unix_link); + PropVariantClear(&pv[0]); + PropVariantClear(&pv[1]); return ret; } @@ -2571,7 +2520,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 @@ -2733,6 +2682,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; @@ -2757,6 +2709,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; @@ -2766,8 +2725,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..81d82ab 100644 --- a/programs/winemenubuilder/winemenubuilder.h +++ b/programs/winemenubuilder/winemenubuilder.h @@ -46,19 +46,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 +70,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.7.10.2