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