Warning, /graphics/digikam/core/libs/threadimageio/engine/dservicemenu_mac.mm is written in an unsupported language. File is not indexed.
0001 /* ============================================================ 0002 * 0003 * This file is a part of digiKam project 0004 * https://www.digikam.org 0005 * 0006 * Date : 2021-01-28 0007 * Description : Objective-C wrapper for open-with operations under MacOS 0008 * 0009 * SPDX-FileCopyrightText: 2021 by Robert Lindsay <robert dot lindsay at gmail dot com> 0010 * SPDX-FileCopyrightText: 2021-2024 by Gilles Caulier <caulier dot gilles at gmail dot com> 0011 * 0012 * SPDX-License-Identifier: GPL-2.0-or-later 0013 * 0014 * ============================================================ */ 0015 0016 #include "dservicemenu.h" 0017 0018 // Qt include 0019 0020 #include <QString> 0021 #include <QList> 0022 #include <QUrl> 0023 #include <QFileInfo> 0024 #include <QIcon> 0025 #include <QPixmap> 0026 #include <QSize> 0027 0028 // MacOS header 0029 0030 #include <AppKit/AppKit.h> 0031 #import <Foundation/Foundation.h> 0032 0033 // Local includes 0034 0035 #include "digikam_debug.h" 0036 #include "digikam_export.h" 0037 0038 namespace Digikam 0039 { 0040 0041 QList<QUrl> DServiceMenu::MacApplicationForFileExtension(const QString& suffix) 0042 { 0043 // Code inspired from: 0044 // qtbase/src/plugins/platforms/cocoa/qcocoanativeinterface.mm : QCocoaNativeInterface::defaultBackgroundPixmapForQWizard() 0045 // qtbase/src/corelib/io/qfilesystemengine_unix.cpp : isPackage() 0046 // qtbase/src/corelib/global/qlibraryinfo.cpp : getRelocatablePrefix() 0047 // qtbase/src/corelib/plugin/qlibrary_unix.cpp : load_sys() 0048 0049 QList<QUrl> appUrls; 0050 0051 if (suffix.isEmpty()) 0052 { 0053 qCWarning(DIGIKAM_GENERAL_LOG) << "Suffix is empty"; 0054 return appUrls; 0055 } 0056 0057 // Make a Uniform Type Identifier from a filename extension. 0058 0059 CFArrayRef bundleIDs = nullptr; 0060 CFStringRef extensionRef = suffix.toCFString(); 0061 CFStringRef uniformTypeIdentifier = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, extensionRef, nullptr); 0062 0063 if (!uniformTypeIdentifier) 0064 { 0065 qCWarning(DIGIKAM_GENERAL_LOG) << "Cannot get the Uniform Type Identifier for" << suffix; 0066 return appUrls; 0067 } 0068 0069 // Get a list of all of the application bundle IDs that know how to handle this. 0070 0071 bundleIDs = LSCopyAllRoleHandlersForContentType(uniformTypeIdentifier, kLSRolesViewer | kLSRolesEditor); 0072 0073 if (bundleIDs) 0074 { 0075 // Find all the available applications with this bundle ID. 0076 // We can also get the display name and version if necessary. 0077 0078 const CFIndex count = CFArrayGetCount(bundleIDs); 0079 0080 for (CFIndex i = 0 ; i < count ; ++i) 0081 { 0082 CFStringRef val = (CFStringRef)(CFArrayGetValueAtIndex(bundleIDs, i)); 0083 CFArrayRef appsForBundleID = LSCopyApplicationURLsForBundleIdentifier(val, nullptr); 0084 0085 if (appsForBundleID) 0086 { 0087 // TODO: call CFURLResourceIsReachable() on each item before to use it. 0088 0089 QList<QUrl> urls; 0090 CFIndex size = CFArrayGetCount(appsForBundleID); 0091 0092 for (CFIndex j = 0 ; j < size ; ++j) 0093 { 0094 CFPropertyListRef prop = CFArrayGetValueAtIndex(appsForBundleID, j); 0095 0096 if (prop) 0097 { 0098 CFTypeID typeId = CFGetTypeID(prop); 0099 0100 if (typeId == CFURLGetTypeID()) 0101 { 0102 urls << QUrl::fromCFURL(static_cast<CFURLRef>(prop)); 0103 } 0104 else 0105 { 0106 qCWarning(DIGIKAM_GENERAL_LOG) << "Application Bundle Property type is not CFURL:" << typeId << "(" << QString::fromCFString(CFCopyTypeIDDescription(typeId)) << ")"; 0107 } 0108 } 0109 else 0110 { 0111 qCWarning(DIGIKAM_GENERAL_LOG) << "Cannot get url" << j << "for application" << i; 0112 } 0113 } 0114 0115 appUrls << urls; 0116 CFRelease(appsForBundleID); 0117 } 0118 } 0119 0120 CFRelease(bundleIDs); 0121 } 0122 else 0123 { 0124 qCWarning(DIGIKAM_GENERAL_LOG) << "Cannot get the Application urls list for" << suffix; 0125 } 0126 0127 // appUrls is a list of ALL possible applications suitable for suffix. 0128 // Given a UI to choose one, you can then call 0129 0130 // Release the resources. 0131 0132 CFRelease(uniformTypeIdentifier); 0133 0134 return appUrls; 0135 } 0136 0137 bool DServiceMenu::MacOpenFilesWithApplication(const QList<QUrl>& fileUrls, const QUrl& appUrl) 0138 { 0139 // Inspired from https://github.com/eep/fugu/blob/master/NSWorkspace(LaunchServices).m 0140 0141 bool success = true; 0142 LSLaunchURLSpec lspec = { nullptr, nullptr, nullptr, 0, nullptr }; 0143 CFMutableArrayRef arrayref = CFArrayCreateMutable(nullptr, 0, nullptr); 0144 0145 Q_FOREACH (const QUrl& fileUrl, fileUrls) 0146 { 0147 CFURLRef furl = fileUrl.toCFURL(); 0148 CFArrayAppendValue(arrayref, furl); 0149 } 0150 0151 lspec.appURL = appUrl.toCFURL(); 0152 lspec.itemURLs = arrayref; 0153 // lspec.passThruParams = params; 0154 // lspec.launchFlags = flags; 0155 lspec.asyncRefCon = nullptr; 0156 0157 OSStatus status = LSOpenFromURLSpec(&lspec, nullptr); 0158 0159 if (status != noErr) 0160 { 0161 success = false; 0162 qCWarning(DIGIKAM_GENERAL_LOG) << "Cannot start Application Bundle url" << appUrl << "for files" << fileUrls; 0163 } 0164 0165 if (arrayref) 0166 { 0167 CFRelease(arrayref); 0168 } 0169 0170 return success; 0171 } 0172 0173 QList<QUrl> DServiceMenu::MacApplicationsForFiles(const QList<QUrl>& files) 0174 { 0175 if (files.isEmpty()) 0176 { 0177 return QList<QUrl>(); 0178 } 0179 0180 QString suffix = QFileInfo(files.first().toLocalFile()).suffix(); 0181 QList<QUrl> commonAppUrls = DServiceMenu::MacApplicationForFileExtension(suffix); 0182 0183 Q_FOREACH (const QUrl& file, files) 0184 { 0185 suffix = QFileInfo(file.toLocalFile()).suffix(); 0186 QList<QUrl> aurls = DServiceMenu::MacApplicationForFileExtension(suffix); 0187 0188 Q_FOREACH (const QUrl& url, aurls) 0189 { 0190 if (!commonAppUrls.contains(url)) 0191 { 0192 commonAppUrls.removeAll(url); 0193 } 0194 } 0195 } 0196 0197 return commonAppUrls; 0198 } 0199 0200 QString DServiceMenu::MacApplicationBundleName(const QUrl& appUrl) 0201 { 0202 return (appUrl.toLocalFile().section(QLatin1Char('/'), -2, -2)); 0203 } 0204 0205 QIcon DServiceMenu::MacApplicationBundleIcon(const QUrl& appUrl, int size) 0206 { 0207 // Inspired from http://theocacao.com/document.page/183 // krazy:exclude=insecurenet 0208 0209 // Get Image from Workspace about Application Bundle. 0210 0211 NSWorkspace* const ws = [NSWorkspace sharedWorkspace]; 0212 NSString* const path = appUrl.path().toNSString(); 0213 NSImage* const macIcon = [ws iconForFile: path]; 0214 0215 // Convert NSImage to QImage to QPixmap to QIcon, using PNG container in memory 0216 0217 CGImageRef cgRef = [macIcon CGImageForProposedRect:NULL context:nil hints:nil]; 0218 NSBitmapImageRep* const bitmap = [[NSBitmapImageRep alloc] initWithCGImage:cgRef]; 0219 [bitmap setSize:[macIcon size]]; 0220 NSData* const pngData = [bitmap representationUsingType:NSBitmapImageFileTypePNG properties:@{}]; 0221 QByteArray array = QByteArray::fromNSData(pngData); 0222 QImage image = QImage::fromData(array, "PNG"); 0223 QPixmap pix = QPixmap::fromImage(image.scaled(size, size)); 0224 0225 // Clenaup 0226 0227 [bitmap autorelease]; 0228 0229 return (QIcon(pix)); 0230 } 0231 0232 } // namespace Digikam 0233