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