summaryrefslogtreecommitdiffstats
path: root/src/gui/platform
diff options
context:
space:
mode:
Diffstat (limited to 'src/gui/platform')
-rw-r--r--src/gui/platform/android/qandroidnativeinterface.cpp18
-rw-r--r--src/gui/platform/darwin/qappleiconengine.mm464
-rw-r--r--src/gui/platform/darwin/qappleiconengine_p.h64
-rw-r--r--src/gui/platform/darwin/qapplekeymapper.mm165
-rw-r--r--src/gui/platform/darwin/qapplekeymapper_p.h14
-rw-r--r--src/gui/platform/darwin/qmacmime.mm1013
-rw-r--r--src/gui/platform/darwin/qmacmime_p.h63
-rw-r--r--src/gui/platform/darwin/qmacmimeregistry.mm118
-rw-r--r--src/gui/platform/darwin/qmacmimeregistry_p.h42
-rw-r--r--src/gui/platform/darwin/qutimimeconverter.h62
-rw-r--r--src/gui/platform/darwin/qutimimeconverter.mm823
-rw-r--r--src/gui/platform/ios/PrivacyInfo.xcprivacy23
-rw-r--r--src/gui/platform/macos/qcocoanativeinterface.mm6
-rw-r--r--src/gui/platform/unix/dbusmenu/qdbusmenuconnection.cpp10
-rw-r--r--src/gui/platform/unix/dbusmenu/qdbusmenuconnection_p.h4
-rw-r--r--src/gui/platform/unix/dbusmenu/qdbusmenuregistrarproxy_p.h2
-rw-r--r--src/gui/platform/unix/dbusmenu/qdbusmenutypes.cpp18
-rw-r--r--src/gui/platform/unix/dbustray/qdbustrayicon.cpp12
-rw-r--r--src/gui/platform/unix/dbustray/qdbustraytypes.cpp6
-rw-r--r--src/gui/platform/unix/dbustray/qstatusnotifieritemadaptor.cpp6
-rw-r--r--src/gui/platform/unix/dbustray/qstatusnotifieritemadaptor_p.h4
-rw-r--r--src/gui/platform/unix/dbustray/qxdgnotificationproxy_p.h2
-rw-r--r--src/gui/platform/unix/qgenericunixservices.cpp367
-rw-r--r--src/gui/platform/unix/qgenericunixservices_p.h10
-rw-r--r--src/gui/platform/unix/qgenericunixthemes.cpp516
-rw-r--r--src/gui/platform/unix/qgenericunixthemes_p.h2
-rw-r--r--src/gui/platform/unix/qunixnativeinterface.cpp97
-rw-r--r--src/gui/platform/unix/qxkbcommon.cpp57
-rw-r--r--src/gui/platform/unix/qxkbcommon_p.h57
-rw-r--r--src/gui/platform/wasm/qlocalfileapi.cpp108
-rw-r--r--src/gui/platform/wasm/qlocalfileapi_p.h31
-rw-r--r--src/gui/platform/wasm/qwasmlocalfileaccess.cpp265
-rw-r--r--src/gui/platform/wasm/qwasmlocalfileaccess_p.h7
-rw-r--r--src/gui/platform/wasm/qwasmnativeinterface.cpp17
-rw-r--r--src/gui/platform/windows/qwindowsguieventdispatcher.cpp2
-rw-r--r--src/gui/platform/windows/qwindowsmimeconverter.cpp170
-rw-r--r--src/gui/platform/windows/qwindowsmimeconverter.h (renamed from src/gui/platform/windows/qwindowsmime_p.h)43
-rw-r--r--src/gui/platform/windows/qwindowsnativeinterface.cpp35
-rw-r--r--src/gui/platform/windows/qwindowsthemecache.cpp79
-rw-r--r--src/gui/platform/windows/qwindowsthemecache_p.h35
40 files changed, 3438 insertions, 1399 deletions
diff --git a/src/gui/platform/android/qandroidnativeinterface.cpp b/src/gui/platform/android/qandroidnativeinterface.cpp
index 3062b5255e..c1c4b7149f 100644
--- a/src/gui/platform/android/qandroidnativeinterface.cpp
+++ b/src/gui/platform/android/qandroidnativeinterface.cpp
@@ -7,6 +7,8 @@
#include <QtGui/qoffscreensurface_platform.h>
#include <QtGui/private/qguiapplication_p.h>
+#include <QtGui/qpa/qplatformscreen_p.h>
+
QT_BEGIN_NAMESPACE
using namespace QNativeInterface::Private;
@@ -33,4 +35,20 @@ QOffscreenSurface *QNativeInterface::QAndroidOffscreenSurface::fromNative(ANati
&QAndroidOffScreenIntegration::createOffscreenSurface>(nativeSurface);
}
+/*!
+ \class QNativeInterface::QAndroidScreen
+ \since 6.7
+ \brief Native interface to a screen.
+
+ Accessed through QScreen::nativeInterface().
+ \inmodule QtGui
+ \ingroup native-interfaces
+ \ingroup native-interfaces-qscreen
+*/
+/*!
+ \fn int QNativeInterface::QAndroidScreen::displayId() const;
+ \return the id of the underlying Android display.
+*/
+QT_DEFINE_NATIVE_INTERFACE(QAndroidScreen);
+
QT_END_NAMESPACE
diff --git a/src/gui/platform/darwin/qappleiconengine.mm b/src/gui/platform/darwin/qappleiconengine.mm
new file mode 100644
index 0000000000..7e0ed184dc
--- /dev/null
+++ b/src/gui/platform/darwin/qappleiconengine.mm
@@ -0,0 +1,464 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include "qappleiconengine_p.h"
+
+#if defined(Q_OS_MACOS)
+# include <AppKit/AppKit.h>
+#elif defined(QT_PLATFORM_UIKIT)
+# include <UIKit/UIKit.h>
+#endif
+
+#include <QtGui/qguiapplication.h>
+#include <QtGui/qpainter.h>
+#include <QtGui/qpalette.h>
+#include <QtGui/qstylehints.h>
+
+#include <QtGui/private/qcoregraphics_p.h>
+
+QT_BEGIN_NAMESPACE
+
+using namespace Qt::StringLiterals;
+
+namespace {
+auto *loadImage(const QString &iconName)
+{
+ static constexpr std::pair<QLatin1StringView, NSString *> iconMap[] = {
+ {"address-book-new"_L1, @"book.closed"},
+ {"application-exit"_L1, @"xmark.circle"},
+ {"appointment-new"_L1, @"calendar.badge.plus"},
+ {"call-start"_L1, @"phone.arrow.up.right"},
+ {"call-stop"_L1, @"phone.down"},
+ {"contact-new"_L1, @"person.crop.circle.badge.plus"},
+ {"document-new"_L1, @"doc.badge.plus"},
+ {"document-open"_L1, @"folder"},
+ {"document-open-recent"_L1, @"doc.badge.clock"},
+ {"document-page-setup"_L1, @"doc.badge.gearshape"},
+ {"document-print"_L1, @"printer"},
+ //{"document-print-preview"_L1, @""},
+ {"document-properties"_L1, @"doc.badge.ellipsis"},
+ //{"document-revert"_L1, @""},
+ {"document-save"_L1, @"square.and.arrow.down"},
+ //{"document-save-as"_L1, @""},
+ {"document-send"_L1, @"paperplane"},
+ {"edit-clear"_L1, @"xmark.circle"},
+ {"edit-copy"_L1, @"doc.on.doc"},
+ {"edit-cut"_L1, @"scissors"},
+ {"edit-delete"_L1, @"delete.left"},
+ {"edit-find"_L1, @"magnifyingglass"},
+ //{"edit-find-replace"_L1, @"arrow.up.left.and.down.right.magnifyingglass"},
+ {"edit-paste"_L1, @"clipboard"},
+ {"edit-redo"_L1, @"arrowshape.turn.up.right"},
+ //{"edit-select-all"_L1, @""},
+ {"edit-undo"_L1, @"arrowshape.turn.up.left"},
+ {"folder-new"_L1, @"folder.badge.plus"},
+ {"format-indent-less"_L1, @"decrease.indent"},
+ {"format-indent-more"_L1, @"increase.indent"},
+ {"format-justify-center"_L1, @"text.aligncenter"},
+ {"format-justify-fill"_L1, @"text.justify"},
+ {"format-justify-left"_L1, @"text.justify.left"},
+ {"format-justify-right"_L1, @"text.justify.right"},
+ {"format-text-direction-ltr"_L1, @"text.justify.leading"},
+ {"format-text-direction-rtl"_L1, @"text.justify.trailing"},
+ {"format-text-bold"_L1, @"bold"},
+ {"format-text-italic"_L1, @"italic"},
+ {"format-text-underline"_L1, @"underline"},
+ {"format-text-strikethrough"_L1, @"strikethrough"},
+ //{"go-bottom"_L1, @""},
+ {"go-down"_L1, @"arrowshape.down"},
+ {"go-first"_L1, @"increase.indent"},
+ {"go-home"_L1, @"house"},
+ //{"go-jump"_L1, @""},
+ //{"go-last"_L1, @""},
+ {"go-next"_L1, @"arrowshape.right"},
+ {"go-previous"_L1, @"arrowshape.left"},
+ //{"go-top"_L1, @""},
+ {"go-up"_L1, @"arrowshape.up"},
+ {"help-about"_L1, @"info.circle"},
+ //{"help-contents"_L1, @""},
+ {"help-faq"_L1, @"questionmark.app"},
+ {"insert-image"_L1, @"photo.badge.plus"},
+ {"insert-link"_L1, @"link.badge.plus"},
+ //{"insert-object"_L1, @""},
+ {"insert-text"_L1, @"textformat"},
+ {"list-add"_L1, @"plus.circle"},
+ {"list-remove"_L1, @"minus.circle"},
+ {"mail-forward"_L1, @"arrowshape.turn.up.right"},
+ {"mail-mark-important"_L1, @"star"},
+ {"mail-mark-junk"_L1, @"xmark.bin"},
+ {"mail-mark-notjunk"_L1, @"trash.slash"},
+ {"mail-mark-read"_L1, @"envelope.open"},
+ {"mail-mark-unread"_L1, @"envelope.fill"},
+ {"mail-message-new"_L1, @"square.and.pencil"},
+ {"mail-reply-all"_L1, @"arrowshape.turn.up.left.2"},
+ {"mail-reply-sender"_L1, @"arrowshape.turn.up.left"},
+ {"mail-send"_L1, @"paperplane"},
+ {"mail-send-receive"_L1, @"envelope.arrow.triangle.branch"},
+ {"media-eject"_L1, @"eject"},
+ {"media-playback-pause"_L1, @"pause"},
+ {"media-playback-start"_L1, @"play"},
+ {"media-playback-stop"_L1, @"stop"},
+ {"media-record"_L1, @"record.circle"},
+ {"media-seek-backward"_L1, @"backward"},
+ {"media-seek-forward"_L1, @"forward"},
+ {"media-skip-backward"_L1, @"backward.end.alt"},
+ {"media-skip-forward"_L1, @"forward.end.alt"},
+ {"object-flip-horizontal"_L1, @"rectangle.landscape.rotate"},
+ {"object-flip-vertical"_L1, @"rectangle.portrait.rotate"},
+ {"object-rotate-left"_L1, @"rotate.left"},
+ {"object-rotate-right"_L1, @"rotate.right"},
+ {"process-stop"_L1, @"stop.circle"},
+ {"system-lock-screen"_L1, @"lock.display"},
+ {"system-log-out"_L1, @"door.left.hand.open"},
+ //{"system-run"_L1, @""},
+ {"system-search"_L1, @"magnifyingglass"},
+ //{"system-reboot"_L1, @""},
+ {"system-shutdown"_L1, @"power"},
+ //{"tools-check-spelling"_L1, @""},
+ {"view-fullscreen"_L1, @"arrow.up.left.and.arrow.down.right"},
+ {"view-refresh"_L1, @"arrow.clockwise"},
+ {"view-restore"_L1, @"arrow.down.right.and.arrow.up.left"},
+ //{"view-sort-ascending"_L1, @""},
+ //{"view-sort-descending"_L1, @""},
+ {"window-close"_L1, @"xmark.circle"},
+ {"window-new"_L1, @"macwindow.badge.plus"},
+ {"zoom-fit-best"_L1, @"square.arrowtriangle.4.outward"},
+ {"zoom-in"_L1, @"plus.magnifyingglass"},
+ //{"zoom-original"_L1, @""},
+ {"zoom-out"_L1, @"minus.magnifyingglass"},
+ {"process-working"_L1, @"circle.dotted"},
+ //{"accessories-calculator"_L1, @""},
+ //{"accessories-character-map"_L1, @""},
+ {"accessories-dictionary"_L1, @"character.book.closed"},
+ {"accessories-text-editor"_L1, @"textformat"},
+ {"help-browser"_L1, @"folder.badge.questionmark"},
+ {"multimedia-volume-control"_L1, @"speaker.wave.3"},
+ {"preferences-desktop-accessibility"_L1, @"accessibility"},
+ //{"preferences-desktop-font"_L1, @""},
+ {"preferences-desktop-keyboard"_L1, @"keyboard.badge.ellipsis"},
+ //{"preferences-desktop-locale"_L1, @""},
+ //{"preferences-desktop-multimedia"_L1, @""},
+ //{"preferences-desktop-screensaver"_L1, @""},
+ //{"preferences-desktop-theme"_L1, @""},
+ //{"preferences-desktop-wallpaper"_L1, @""},
+ {"system-file-manager"_L1, @"folder.badge.gearshape"},
+ //{"system-software-install"_L1, @""},
+ //{"system-software-update"_L1, @""}, d
+ //{"utilities-system-monitor"_L1, @""},
+ {"utilities-terminal"_L1, @"apple.terminal"},
+ //{"applications-accessories"_L1, @""},
+ //{"applications-development"_L1, @""},
+ //{"applications-engineering"_L1, @""},
+ {"applications-games"_L1, @"gamecontroller"},
+ //{"applications-graphics"_L1, @""},
+ {"applications-internet"_L1, @"network"},
+ {"applications-multimedia"_L1, @"tv.and.mediabox"},
+ //{"applications-office"_L1, @""},
+ //{"applications-other"_L1, @""},
+ {"applications-science"_L1, @"atom"},
+ //{"applications-system"_L1, @""},
+ //{"applications-utilities"_L1, @""},
+ {"preferences-desktop"_L1, @"menubar.dock.rectangle"},
+ //{"preferences-desktop-peripherals"_L1, @""},
+ //{"preferences-desktop-personal"_L1, @""},
+ //{"preferences-other"_L1, @""},
+ //{"preferences-system"_L1, @""},
+ {"preferences-system-network"_L1, @"network"},
+ {"system-help"_L1, @"questionmark.diamond"},
+ {"audio-card"_L1, @"waveform.circle"},
+ {"audio-input-microphone"_L1, @"mic"},
+ {"battery"_L1, @"battery.100percent"},
+ {"camera-photo"_L1, @"camera"},
+ {"camera-video"_L1, @"video"},
+ {"camera-web"_L1, @"web.camera"},
+ {"computer"_L1, @"desktopcomputer"},
+ {"drive-harddisk"_L1, @"internaldrive"},
+ {"drive-optical"_L1, @"opticaldiscdrive"},
+ {"drive-removable-media"_L1, @"externaldrive"},
+ {"input-gaming"_L1, @"gamecontroller"}, // "games" also using this one
+ {"input-keyboard"_L1, @"keyboard"},
+ {"input-mouse"_L1, @"computermouse"},
+ {"input-tablet"_L1, @"ipad"},
+ {"media-flash"_L1, @"mediastick"},
+ //{"media-floppy"_L1, @""},
+ //{"media-optical"_L1, @""},
+ {"media-tape"_L1, @"recordingtape"},
+ //{"modem"_L1, @""},
+ {"multimedia-player"_L1, @"play.rectangle"},
+ {"network-wired"_L1, @"app.connected.to.app.below.fill"},
+ {"network-wireless"_L1, @"wifi"},
+ //{"pda"_L1, @""},
+ {"phone"_L1, @"iphone"},
+ {"printer"_L1, @"printer"},
+ {"scanner"_L1, @"scanner"},
+ {"video-display"_L1, @"play.display"},
+ //{"emblem-default"_L1, @""},
+ {"emblem-documents"_L1, @"doc.circle"},
+ {"emblem-downloads"_L1, @"arrow.down.circle"},
+ {"emblem-favorite"_L1, @"star"},
+ {"emblem-important"_L1, @"exclamationmark.bubble.circle"},
+ {"emblem-mail"_L1, @"envelope"},
+ {"emblem-photos"_L1, @"photo.stack"},
+ //{"emblem-readonly"_L1, @""},
+ {"emblem-shared"_L1, @"folder.badge.person.crop"},
+ {"emblem-symbolic-link"_L1, @"link.circle"},
+ {"emblem-synchronized"_L1, @"arrow.triangle.2.circlepath.circle"},
+ {"emblem-system"_L1, @"gear"},
+ //{"emblem-unreadable"_L1, @""},
+ {"folder"_L1, @"folder"},
+ //{"folder-remote"_L1, @""},
+ {"network-server"_L1, @"server.rack"},
+ //{"network-workgroup"_L1, @""},
+ //{"start-here"_L1, @""},
+ {"user-bookmarks"_L1, @"bookmark.circle"},
+ {"user-desktop"_L1, @"desktopcomputer"}, //"computer" also using this one
+ {"user-home"_L1, @"house"}, //"go-home" also using this one
+ {"user-trash"_L1, @"trash"},
+ {"appointment-missed"_L1, @"calendar.badge.exclamationmark"},
+ {"appointment-soon"_L1, @"calendar.badge.clock"},
+ {"audio-volume-high"_L1, @"speaker.wave.3"},
+ {"audio-volume-low"_L1, @"speaker.wave.1"},
+ {"audio-volume-medium"_L1, @"speaker.wave.2"},
+ {"audio-volume-muted"_L1, @"speaker.slash"},
+ {"battery-caution"_L1, @"minus.plus.batteryblock.exclamationmark"},
+ {"battery-low"_L1, @"battery.25percent"}, // there are different levels that can be low battery
+ {"dialog-error"_L1, @"exclamationmark.bubble"},
+ {"dialog-information"_L1, @"info.circle"},
+ {"dialog-password"_L1, @"lock"},
+ {"dialog-question"_L1, @"questionmark.circle"},
+ {"dialog-warning"_L1, @"exclamationmark.octagon"},
+ {"folder-drag-accept"_L1, @"plus.rectangle.on.folder"},
+ //{"folder-open"_L1, @""},
+ {"folder-visiting"_L1, @"folder.circle"},
+ {"image-loading"_L1, @"photo.circle"},
+ {"image-missing"_L1, @"photo"},
+ {"mail-attachment"_L1, @"paperclip"},
+ {"mail-unread"_L1, @"envelope.badge"},
+ {"mail-read"_L1, @"envelope.open"},
+ {"mail-replied"_L1, @"arrowshape.turn.up.left"},
+ //{"mail-signed"_L1, @""},
+ //{"mail-signed-verified"_L1, @""},
+ {"media-playlist-repeat"_L1, @"repet"},
+ {"media-playlist-shuffle"_L1, @"shuffle"},
+ //{"network-error"_L1, @""},
+ //{"network-idle"_L1, @""},
+ {"network-offline"_L1, @"network.slash"},
+ //{"network-receive"_L1, @""},
+ //{"network-transmit"_L1, @""},
+ //{"network-transmit-receive"_L1, @""},
+ //{"printer-error"_L1, @""},
+ {"printer-printing"_L1, @"printer.dotmatrix.filled.and.paper"}, // not sure
+ {"security-high"_L1, @"lock.shield"},
+ //{"security-medium"_L1, @""},
+ {"security-low"_L1, @"lock.trianglebadge.exclamationmark"},
+ {"software-update-available"_L1, @"arrowshape.up.circle"},
+ {"software-update-urgent"_L1, @"exclamationmark.transmission"},
+ {"sync-error"_L1, @"exclamationmark.arrow.triangle.2.circlepath"},
+ {"sync-synchronizing"_L1, @"arrow.triangle.2.circlepath"},
+ {"task-due"_L1, @"clock.badge.exclamationmark"},
+ {"task-past-due"_L1, @"clock.badge.xmark"},
+ {"user-available"_L1, @"person.crop.circle.badge.checkmark"},
+ {"user-away"_L1, @"person.crop.circle.badge.clock"},
+ //{"user-idle"_L1, @""},
+ {"user-offline"_L1, @"person.crop.circle.badge.xmark"},
+ //{"user-trash-full"_L1, @""},
+ {"weather-clear"_L1, @"sun.max"},
+ {"weather-clear-night"_L1, @"moon"},
+ {"weather-few-clouds"_L1, @"cloud.sun"},
+ {"weather-few-clouds-night"_L1, @"cloud.moon"},
+ {"weather-fog"_L1, @"cloud.fog"},
+ {"weather-overcast"_L1, @"cloud"},
+ //{"weather-severe-alert"_L1, @""},
+ {"weather-showers"_L1, @"cloud.rain"},
+ //{"weather-showers-scattered"_L1, @""},
+ {"weather-snow"_L1, @"cloud.snow"},
+ {"weather-storm"_L1, @"tropicalstorm"},
+ };
+ const auto it = std::find_if(std::begin(iconMap), std::end(iconMap), [iconName](const auto &c){
+ return c.first == iconName;
+ });
+ NSString *systemIconName = it != std::end(iconMap) ? it->second : iconName.toNSString();
+#if defined(Q_OS_MACOS)
+ return [NSImage imageWithSystemSymbolName:systemIconName accessibilityDescription:nil];
+#elif defined(QT_PLATFORM_UIKIT)
+ return [UIImage systemImageNamed:systemIconName];
+#endif
+}
+}
+
+QAppleIconEngine::QAppleIconEngine(const QString &iconName)
+ : m_iconName(iconName), m_image(loadImage(iconName))
+{
+ if (m_image)
+ [m_image retain];
+}
+
+QAppleIconEngine::~QAppleIconEngine()
+{
+ if (m_image)
+ [m_image release];
+}
+
+QIconEngine *QAppleIconEngine::clone() const
+{
+ return new QAppleIconEngine(m_iconName);
+}
+
+QString QAppleIconEngine::key() const
+{
+ return u"QAppleIconEngine"_s;
+}
+
+QString QAppleIconEngine::iconName()
+{
+ return m_iconName;
+}
+
+bool QAppleIconEngine::isNull()
+{
+ return m_image == nullptr;
+}
+
+QList<QSize> QAppleIconEngine::availableIconSizes(double aspectRatio)
+{
+ const qreal devicePixelRatio = qGuiApp->devicePixelRatio();
+ const QList<QSize> sizes = {
+ {qRound(16 * devicePixelRatio), qRound(16. * devicePixelRatio / aspectRatio)},
+ {qRound(32 * devicePixelRatio), qRound(32. * devicePixelRatio / aspectRatio)},
+ {qRound(64 * devicePixelRatio), qRound(64. * devicePixelRatio / aspectRatio)},
+ {qRound(128 * devicePixelRatio), qRound(128. * devicePixelRatio / aspectRatio)},
+ {qRound(256 * devicePixelRatio), qRound(256. * devicePixelRatio / aspectRatio)},
+ };
+ return sizes;
+}
+
+QList<QSize> QAppleIconEngine::availableSizes(QIcon::Mode, QIcon::State)
+{
+ const double aspectRatio = isNull() ? 1.0 : m_image.size.width / m_image.size.height;
+ return availableIconSizes(aspectRatio);
+}
+
+QSize QAppleIconEngine::actualSize(const QSize &size, QIcon::Mode /*mode*/, QIcon::State /*state*/)
+{
+ const double inputAspectRatio = isNull() ? 1.0 : m_image.size.width / m_image.size.height;
+ const double outputAspectRatio = size.width() / size.height();
+ QSize result = size;
+ if (outputAspectRatio > inputAspectRatio)
+ result.rwidth() = result.height() * inputAspectRatio;
+ else
+ result.rheight() = result.width() / inputAspectRatio;
+ return result;
+}
+
+QPixmap QAppleIconEngine::pixmap(const QSize &size, QIcon::Mode mode, QIcon::State state)
+{
+ return scaledPixmap(size, mode, state, 1.0);
+}
+
+namespace {
+#if defined(Q_OS_MACOS)
+auto *configuredImage(const NSImage *image, const QColor &color)
+{
+ auto *config = [NSImageSymbolConfiguration configurationWithPointSize:48
+ weight:NSFontWeightRegular
+ scale:NSImageSymbolScaleLarge];
+ if (@available(macOS 12, *)) {
+ auto *primaryColor = [NSColor colorWithSRGBRed:color.redF()
+ green:color.greenF()
+ blue:color.blueF()
+ alpha:color.alphaF()];
+
+ auto *colorConfig = [NSImageSymbolConfiguration configurationWithHierarchicalColor:primaryColor];
+ config = [config configurationByApplyingConfiguration:colorConfig];
+ }
+
+ return [image imageWithSymbolConfiguration:config];
+}
+#elif defined(QT_PLATFORM_UIKIT)
+auto *configuredImage(const UIImage *image, const QColor &color)
+{
+ auto *config = [UIImageSymbolConfiguration configurationWithPointSize:48
+ weight:UIImageSymbolWeightRegular
+ scale:UIImageSymbolScaleLarge];
+
+ if (@available(iOS 15, *)) {
+ auto *primaryColor = [UIColor colorWithRed:color.redF()
+ green:color.greenF()
+ blue:color.blueF()
+ alpha:color.alphaF()];
+
+ auto *colorConfig = [UIImageSymbolConfiguration configurationWithHierarchicalColor:primaryColor];
+ config = [config configurationByApplyingConfiguration:colorConfig];
+ }
+ return [image imageByApplyingSymbolConfiguration:config];
+}
+#endif
+}
+
+QPixmap QAppleIconEngine::scaledPixmap(const QSize &size, QIcon::Mode mode, QIcon::State state, qreal scale)
+{
+ const quint64 cacheKey = calculateCacheKey(mode, state);
+ if (cacheKey != m_cacheKey || m_pixmap.size() != size || m_pixmap.devicePixelRatio() != scale) {
+ const QSize paintSize = actualSize(size, mode, state);
+ const QSize paintOffset = paintSize != size
+ ? (QSizeF(size - paintSize) * 0.5).toSize()
+ : QSize();
+
+ m_pixmap = QPixmap(size * scale);
+ m_pixmap.setDevicePixelRatio(scale);
+ m_pixmap.fill(Qt::transparent);
+
+ QPainter painter(&m_pixmap);
+ paint(&painter, QRect(paintOffset.width(), paintOffset.height(),
+ paintSize.width(), paintSize.height()), mode, state);
+
+ m_cacheKey = cacheKey;
+ }
+ return m_pixmap;
+}
+
+void QAppleIconEngine::paint(QPainter *painter, const QRect &rect, QIcon::Mode mode, QIcon::State state)
+{
+ Q_UNUSED(state);
+
+ QColor color;
+ const QPalette palette;
+ switch (mode) {
+ case QIcon::Normal:
+ color = palette.color(QPalette::Inactive, QPalette::Text);
+ break;
+ case QIcon::Disabled:
+ color = palette.color(QPalette::Disabled, QPalette::Text);
+ break;
+ case QIcon::Active:
+ color = palette.color(QPalette::Active, QPalette::Text);
+ break;
+ case QIcon::Selected:
+ color = palette.color(QPalette::Active, QPalette::HighlightedText);
+ break;
+ }
+ const auto *image = configuredImage(m_image, color);
+
+ QMacCGContext ctx(painter);
+
+#if defined(Q_OS_MACOS)
+ NSGraphicsContext *gc = [NSGraphicsContext graphicsContextWithCGContext:ctx flipped:YES];
+ [NSGraphicsContext saveGraphicsState];
+ [NSGraphicsContext setCurrentContext:gc];
+
+ const NSSize pixmapSize = NSMakeSize(rect.width(), rect.height());
+ [image setSize:pixmapSize];
+ const NSRect sourceRect = NSMakeRect(0, 0, pixmapSize.width, pixmapSize.height);
+ const NSRect iconRect = NSMakeRect(rect.x(), rect.y(), pixmapSize.width, pixmapSize.height);
+
+ [image drawInRect:iconRect fromRect:sourceRect operation:NSCompositingOperationSourceOver fraction:1.0 respectFlipped:YES hints:nil];
+ [NSGraphicsContext restoreGraphicsState];
+#elif defined(QT_PLATFORM_UIKIT)
+ UIGraphicsPushContext(ctx);
+ const CGRect cgrect = CGRectMake(rect.x(), rect.y(), rect.width(), rect.height());
+ [image drawInRect:cgrect];
+ UIGraphicsPopContext();
+#endif
+}
+
+QT_END_NAMESPACE
diff --git a/src/gui/platform/darwin/qappleiconengine_p.h b/src/gui/platform/darwin/qappleiconengine_p.h
new file mode 100644
index 0000000000..2a4ff7fc64
--- /dev/null
+++ b/src/gui/platform/darwin/qappleiconengine_p.h
@@ -0,0 +1,64 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef QAPPLEICONENGINE_P_H
+#define QAPPLEICONENGINE_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <QtGui/qiconengine.h>
+
+#include <QtCore/private/qcore_mac_p.h>
+
+Q_FORWARD_DECLARE_OBJC_CLASS(UIImage);
+Q_FORWARD_DECLARE_OBJC_CLASS(NSImage);
+
+QT_BEGIN_NAMESPACE
+
+class Q_GUI_EXPORT QAppleIconEngine : public QIconEngine
+{
+public:
+ QAppleIconEngine(const QString &iconName);
+ ~QAppleIconEngine();
+ QIconEngine *clone() const override;
+ QString key() const override;
+ QString iconName() override;
+ bool isNull() override;
+
+ QList<QSize> availableSizes(QIcon::Mode, QIcon::State) override;
+ QSize actualSize(const QSize &size, QIcon::Mode mode, QIcon::State state) override;
+ QPixmap pixmap(const QSize &size, QIcon::Mode mode, QIcon::State state) override;
+ QPixmap scaledPixmap(const QSize &size, QIcon::Mode mode, QIcon::State state, qreal scale) override;
+ void paint(QPainter *painter, const QRect &rect, QIcon::Mode mode, QIcon::State state) override;
+
+ static QList<QSize> availableIconSizes(double aspectRatio = 1.0);
+
+private:
+ static constexpr quint64 calculateCacheKey(QIcon::Mode mode, QIcon::State state)
+ {
+ return (quint64(mode) << 32) | state;
+ }
+
+ const QString m_iconName;
+#if defined(Q_OS_MACOS)
+ const NSImage *m_image;
+#elif defined(QT_PLATFORM_UIKIT)
+ const UIImage *m_image;
+#endif
+ mutable QPixmap m_pixmap;
+ mutable quint64 m_cacheKey = {};
+};
+
+
+QT_END_NAMESPACE
+
+#endif // QAPPLEICONENGINE_P_H
diff --git a/src/gui/platform/darwin/qapplekeymapper.mm b/src/gui/platform/darwin/qapplekeymapper.mm
index dcc51bfca6..b8ff5c9d6d 100644
--- a/src/gui/platform/darwin/qapplekeymapper.mm
+++ b/src/gui/platform/darwin/qapplekeymapper.mm
@@ -18,7 +18,6 @@
QT_BEGIN_NAMESPACE
-Q_LOGGING_CATEGORY(lcQpaKeyMapper, "qt.qpa.keymapper");
Q_LOGGING_CATEGORY(lcQpaKeyMapperKeys, "qt.qpa.keymapper.keys");
static Qt::KeyboardModifiers swapModifiersIfNeeded(const Qt::KeyboardModifiers modifiers)
@@ -37,36 +36,6 @@ static Qt::KeyboardModifiers swapModifiersIfNeeded(const Qt::KeyboardModifiers m
return swappedModifiers;
}
-Qt::Key QAppleKeyMapper::fromNSString(Qt::KeyboardModifiers qtModifiers, NSString *characters,
- NSString *charactersIgnoringModifiers, QString &text)
-{
- if ([characters isEqualToString:@"\t"]) {
- if (qtModifiers & Qt::ShiftModifier)
- return Qt::Key_Backtab;
- return Qt::Key_Tab;
- } else if ([characters isEqualToString:@"\r"]) {
- if (qtModifiers & Qt::KeypadModifier)
- return Qt::Key_Enter;
- return Qt::Key_Return;
- }
- if ([characters length] != 0 || [charactersIgnoringModifiers length] != 0) {
- QChar ch;
- if (((qtModifiers & Qt::MetaModifier) || (qtModifiers & Qt::AltModifier)) &&
- ([charactersIgnoringModifiers length] != 0)) {
- ch = QChar([charactersIgnoringModifiers characterAtIndex:0]);
- } else if ([characters length] != 0) {
- ch = QChar([characters characterAtIndex:0]);
- }
- if (!(qtModifiers & (Qt::ControlModifier | Qt::MetaModifier)) &&
- (ch.unicode() < 0xf700 || ch.unicode() > 0xf8ff)) {
- text = QString::fromNSString(characters);
- }
- if (!ch.isNull())
- return Qt::Key(ch.toUpper().unicode());
- }
- return Qt::Key_unknown;
-}
-
#ifdef Q_OS_MACOS
static constexpr std::tuple<NSEventModifierFlags, Qt::KeyboardModifier> cocoaModifierMap[] = {
{ NSEventModifierFlagShift, Qt::ShiftModifier },
@@ -360,11 +329,11 @@ QChar QAppleKeyMapper::toCocoaKey(Qt::Key key)
{
// Prioritize overloaded keys
if (key == Qt::Key_Return)
- return QChar(NSNewlineCharacter);
+ return QChar(NSCarriageReturnCharacter);
if (key == Qt::Key_Backspace)
return QChar(NSBackspaceCharacter);
- static QHash<Qt::Key, char16_t> reverseCocoaKeys;
+ Q_CONSTINIT static QHash<Qt::Key, char16_t> reverseCocoaKeys;
if (reverseCocoaKeys.isEmpty()) {
reverseCocoaKeys.reserve(cocoaKeys.size());
for (auto it = cocoaKeys.begin(); it != cocoaKeys.end(); ++it)
@@ -384,7 +353,7 @@ Qt::Key QAppleKeyMapper::fromCocoaKey(QChar keyCode)
// ------------------------------------------------
-Qt::KeyboardModifiers QAppleKeyMapper::queryKeyboardModifiers()
+Qt::KeyboardModifiers QAppleKeyMapper::queryKeyboardModifiers() const
{
return fromCocoaModifiers(NSEvent.modifierFlags);
}
@@ -538,11 +507,9 @@ const QAppleKeyMapper::KeyMap &QAppleKeyMapper::keyMapForKey(VirtualKeyCode virt
where each modifier-key combination has been mapped to the
key it will produce.
*/
-QList<int> QAppleKeyMapper::possibleKeys(const QKeyEvent *event) const
+QList<QKeyCombination> QAppleKeyMapper::possibleKeyCombinations(const QKeyEvent *event) const
{
- QList<int> ret;
-
- qCDebug(lcQpaKeyMapper) << "Computing possible keys for" << event;
+ QList<QKeyCombination> ret;
const auto nativeVirtualKey = event->nativeVirtualKey();
if (!nativeVirtualKey)
@@ -555,16 +522,49 @@ QList<int> QAppleKeyMapper::possibleKeys(const QKeyEvent *event) const
auto eventModifiers = event->modifiers();
- // The complete set of event modifiers, along with the
- // unmodified key, is always a valid key combination,
- // and the first priority.
- ret << int(eventModifiers) + int(unmodifiedKey);
+ int startingModifierLayer = 0;
+ if (toCocoaModifiers(eventModifiers) & NSEventModifierFlagCommand) {
+ // When the Command key is pressed AppKit seems to do key equivalent
+ // matching using a Latin/Roman interpretation of the current keyboard
+ // layout. For example, for a Greek layout, pressing Option+Command+C
+ // produces a key event with chars="ç" and unmodchars="ψ", but AppKit
+ // still treats this as a match for a key equivalent of Option+Command+C.
+ // We can't do the same by just applying the modifiers to our key map,
+ // as that too contains "ψ" for the Option+Command combination. What we
+ // can do instead is take advantage of the fact that the Command
+ // modifier layer in all/most keyboard layouts contains a Latin
+ // layer. We then combine that with the modifiers of the event
+ // to produce the resulting "Latin" key combination.
+ static constexpr int kCommandLayer = 2;
+ ret << QKeyCombination::fromCombined(
+ int(eventModifiers) + int(keyMap[kCommandLayer]));
+
+ // If the unmodified key is outside of Latin1, we also treat
+ // that as a valid key combination, even if AppKit natively
+ // does not. For example, for a Greek layout, we still want
+ // to support Option+Command+ψ as a key combination, as it's
+ // unlikely to clash with the Latin key combination we added
+ // above.
+
+ // However, if the unmodified key is within Latin1, we skip
+ // it, to avoid these types of conflicts. For example, in
+ // the same Greek layout, pressing the key next to Tab will
+ // produce a Latin ';' symbol, but we've already treated that
+ // as 'q' above, thanks to the Command modifier, so we skip
+ // the potential Command+; key combination. This is also in
+ // line with what AppKit natively does.
+
+ // Skipping Latin1 unmodified keys also handles the case of
+ // a Latin layout, where the unmodified and modified keys
+ // are the same.
+
+ if (unmodifiedKey <= 0xff)
+ startingModifierLayer = 1;
+ }
// FIXME: We only compute the first 8 combinations. Why?
- for (int i = 1; i < 8; ++i) {
+ for (int i = startingModifierLayer; i < 15; ++i) {
auto keyAfterApplyingModifiers = keyMap[i];
- if (keyAfterApplyingModifiers == unmodifiedKey)
- continue;
if (!keyAfterApplyingModifiers)
continue;
@@ -575,18 +575,39 @@ QList<int> QAppleKeyMapper::possibleKeys(const QKeyEvent *event) const
// If the event includes more modifiers than the candidate they
// will need to be included in the resulting key combination.
auto additionalModifiers = eventModifiers & ~candidateModifiers;
- ret << int(additionalModifiers) + int(keyAfterApplyingModifiers);
- }
- }
- if (lcQpaKeyMapper().isDebugEnabled()) {
- qCDebug(lcQpaKeyMapper) << "Possible keys:";
- for (int keyAndModifiers : ret) {
- auto keyCombination = QKeyCombination::fromCombined(keyAndModifiers);
- auto keySequence = QKeySequence(keyCombination);
- qCDebug(lcQpaKeyMapper).verbosity(0) << "\t-"
- << keyCombination << "/" << keySequence << "/"
- << qUtf8Printable(keySequence.toString(QKeySequence::NativeText));
+ auto keyCombination = QKeyCombination::fromCombined(
+ int(additionalModifiers) + int(keyAfterApplyingModifiers));
+
+ // If there's an existing key combination with the same key,
+ // but a different set of modifiers, we want to choose only
+ // one of them, by priority (see below).
+ const auto existingCombination = std::find_if(
+ ret.begin(), ret.end(), [&](auto existingCombination) {
+ return existingCombination.key() == keyAfterApplyingModifiers;
+ });
+
+ if (existingCombination != ret.end()) {
+ // We prioritize the combination with the more specific
+ // modifiers. In the case where the number of modifiers
+ // are the same, we want to prioritize Command over Option
+ // over Control over Shift. Unfortunately the order (and
+ // hence value) of the modifiers in Qt::KeyboardModifier
+ // does not match our preferred order when Control and
+ // Meta is switched, but we can work around that by
+ // explicitly swapping the modifiers and using that
+ // for the comparison. This also works when the
+ // Qt::AA_MacDontSwapCtrlAndMeta application attribute
+ // is set, as the incoming modifiers are then left
+ // as is, and we can still trust the order.
+ auto existingModifiers = swapModifiersIfNeeded(existingCombination->keyboardModifiers());
+ auto replacementModifiers = swapModifiersIfNeeded(additionalModifiers);
+ if (replacementModifiers > existingModifiers)
+ *existingCombination = keyCombination;
+ } else {
+ // All is good, no existing combination has this key
+ ret << keyCombination;
+ }
}
}
@@ -597,11 +618,40 @@ QList<int> QAppleKeyMapper::possibleKeys(const QKeyEvent *event) const
#else // iOS
+Qt::Key QAppleKeyMapper::fromNSString(Qt::KeyboardModifiers qtModifiers, NSString *characters,
+ NSString *charactersIgnoringModifiers, QString &text)
+{
+ if ([characters isEqualToString:@"\t"]) {
+ if (qtModifiers & Qt::ShiftModifier)
+ return Qt::Key_Backtab;
+ return Qt::Key_Tab;
+ } else if ([characters isEqualToString:@"\r"]) {
+ if (qtModifiers & Qt::KeypadModifier)
+ return Qt::Key_Enter;
+ return Qt::Key_Return;
+ }
+ if ([characters length] != 0 || [charactersIgnoringModifiers length] != 0) {
+ QChar ch;
+ if (((qtModifiers & Qt::MetaModifier) || (qtModifiers & Qt::AltModifier)) &&
+ ([charactersIgnoringModifiers length] != 0)) {
+ ch = QChar([charactersIgnoringModifiers characterAtIndex:0]);
+ } else if ([characters length] != 0) {
+ ch = QChar([characters characterAtIndex:0]);
+ }
+ if (!(qtModifiers & (Qt::ControlModifier | Qt::MetaModifier)) &&
+ (ch.unicode() < 0xf700 || ch.unicode() > 0xf8ff)) {
+ text = QString::fromNSString(characters);
+ }
+ if (!ch.isNull())
+ return Qt::Key(ch.toUpper().unicode());
+ }
+ return Qt::Key_unknown;
+}
+
// Keyboard keys (non-modifiers)
API_AVAILABLE(ios(13.4)) Qt::Key QAppleKeyMapper::fromUIKitKey(NSString *keyCode)
{
static QHash<NSString *, Qt::Key> uiKitKeys = {
-#if QT_IOS_PLATFORM_SDK_EQUAL_OR_ABOVE(__IPHONE_13_4)
{ UIKeyInputF1, Qt::Key_F1 },
{ UIKeyInputF2, Qt::Key_F2 },
{ UIKeyInputF3, Qt::Key_F3 },
@@ -618,7 +668,6 @@ API_AVAILABLE(ios(13.4)) Qt::Key QAppleKeyMapper::fromUIKitKey(NSString *keyCode
{ UIKeyInputEnd, Qt::Key_End },
{ UIKeyInputPageUp, Qt::Key_PageUp },
{ UIKeyInputPageDown, Qt::Key_PageDown },
-#endif
{ UIKeyInputEscape, Qt::Key_Escape },
{ UIKeyInputUpArrow, Qt::Key_Up },
{ UIKeyInputDownArrow, Qt::Key_Down },
diff --git a/src/gui/platform/darwin/qapplekeymapper_p.h b/src/gui/platform/darwin/qapplekeymapper_p.h
index 34557c8ede..1f3494d16f 100644
--- a/src/gui/platform/darwin/qapplekeymapper_p.h
+++ b/src/gui/platform/darwin/qapplekeymapper_p.h
@@ -19,6 +19,8 @@
#include <Carbon/Carbon.h>
#endif
+#include <qpa/qplatformkeymapper.h>
+
#include <QtCore/QList>
#include <QtCore/QHash>
#include <QtGui/QKeyEvent>
@@ -27,13 +29,12 @@
QT_BEGIN_NAMESPACE
-class Q_GUI_EXPORT QAppleKeyMapper
+class Q_GUI_EXPORT QAppleKeyMapper : public QPlatformKeyMapper
{
public:
- static Qt::KeyboardModifiers queryKeyboardModifiers();
- QList<int> possibleKeys(const QKeyEvent *event) const;
- static Qt::Key fromNSString(Qt::KeyboardModifiers qtMods, NSString *characters,
- NSString *charactersIgnoringModifiers, QString &text);
+ Qt::KeyboardModifiers queryKeyboardModifiers() const override;
+ QList<QKeyCombination> possibleKeyCombinations(const QKeyEvent *event) const override;
+
#ifdef Q_OS_MACOS
static Qt::KeyboardModifiers fromCocoaModifiers(NSEventModifierFlags cocoaModifiers);
static NSEventModifierFlags toCocoaModifiers(Qt::KeyboardModifiers);
@@ -41,6 +42,9 @@ public:
static QChar toCocoaKey(Qt::Key key);
static Qt::Key fromCocoaKey(QChar keyCode);
#else
+ static Qt::Key fromNSString(Qt::KeyboardModifiers qtMods, NSString *characters,
+ NSString *charactersIgnoringModifiers, QString &text);
+
static Qt::Key fromUIKitKey(NSString *keyCode);
static Qt::KeyboardModifiers fromUIKitModifiers(ulong uikitModifiers);
static ulong toUIKitModifiers(Qt::KeyboardModifiers);
diff --git a/src/gui/platform/darwin/qmacmime.mm b/src/gui/platform/darwin/qmacmime.mm
deleted file mode 100644
index fe323bf60d..0000000000
--- a/src/gui/platform/darwin/qmacmime.mm
+++ /dev/null
@@ -1,1013 +0,0 @@
-// Copyright (C) 2016 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
-
-#include <ImageIO/ImageIO.h>
-
-#include <QtCore/qsystemdetection.h>
-#include <QtCore/qurl.h>
-#include <QtGui/qimage.h>
-#include <QtCore/qmimedata.h>
-#include <QtCore/qstringconverter.h>
-
-#if defined(Q_OS_MACOS)
-#import <AppKit/AppKit.h>
-#else
-#include <MobileCoreServices/MobileCoreServices.h>
-#endif
-
-#if defined(QT_PLATFORM_UIKIT)
-#import <UIKit/UIKit.h>
-#endif
-
-#include "qmacmime_p.h"
-#include "qguiapplication.h"
-#include "private/qcore_mac_p.h"
-
-QT_BEGIN_NAMESPACE
-
-using namespace Qt::StringLiterals;
-
-typedef QList<QMacInternalPasteboardMime*> MimeList;
-Q_GLOBAL_STATIC(MimeList, globalMimeList)
-Q_GLOBAL_STATIC(QStringList, globalDraggedTypesList)
-
-void qt_mac_addToGlobalMimeList(QMacInternalPasteboardMime *macMime)
-{
- // globalMimeList is in decreasing priority order. Recently added
- // converters take prioity over previously added converters: prepend
- // to the list.
- globalMimeList()->prepend(macMime);
-}
-
-void qt_mac_removeFromGlobalMimeList(QMacInternalPasteboardMime *macMime)
-{
- if (!QGuiApplication::closingDown())
- globalMimeList()->removeAll(macMime);
-}
-
-/*!
- \fn void qRegisterDraggedTypes(const QStringList &types)
- \relates QMacPasteboardMime
-
- Registers the given \a types as custom pasteboard types.
-
- This function should be called to enable the Drag and Drop events
- for custom pasteboard types on Cocoa implementations. This is required
- in addition to a QMacPasteboardMime subclass implementation. By default
- drag and drop is enabled for all standard pasteboard types.
-
- \sa QMacPasteboardMime
-*/
-void qt_mac_registerDraggedTypes(const QStringList &types)
-{
- (*globalDraggedTypesList()) += types;
-}
-
-const QStringList& qt_mac_enabledDraggedTypes()
-{
- return (*globalDraggedTypesList());
-}
-
-/*****************************************************************************
- QDnD debug facilities
- *****************************************************************************/
-//#define DEBUG_MIME_MAPS
-
-/*!
- \class QMacInternalPasteboardMime
- \internal
- \brief The QMacPasteboardMime class converts between a MIME type and a
- \l{http://developer.apple.com/macosx/uniformtypeidentifiers.html}{Uniform
- Type Identifier (UTI)} format.
- \since 4.2
-
- \ingroup draganddrop
- \inmodule QtWidgets
-
- Qt's drag and drop and clipboard facilities use the MIME
- standard. On X11, this maps trivially to the Xdnd protocol. On
- Mac, although some applications use MIME to describe clipboard
- contents, it is more common to use Apple's UTI format.
-
- QMacPasteboardMime's role is to bridge the gap between MIME and UTI;
- By subclasses this class, one can extend Qt's drag and drop
- and clipboard handling to convert to and from unsupported, or proprietary, UTI formats.
-
- A subclass of QMacPasteboardMime will automatically be registered, and active, upon instantiation.
-
- Qt has predefined support for the following UTIs:
- \list
- \li public.utf8-plain-text - converts to "text/plain"
- \li public.utf16-plain-text - converts to "text/plain"
- \li public.text - converts to "text/plain"
- \li public.html - converts to "text/html"
- \li public.url - converts to "text/uri-list"
- \li public.file-url - converts to "text/uri-list"
- \li public.tiff - converts to "application/x-qt-image"
- \li public.vcard - converts to "text/plain"
- \li com.apple.traditional-mac-plain-text - converts to "text/plain"
- \li com.apple.pict - converts to "application/x-qt-image"
- \endlist
-
- When working with MIME data, Qt will iterate through all instances of QMacPasteboardMime to
- find an instance that can convert to, or from, a specific MIME type. It will do this by calling
- canConvert() on each instance, starting with (and choosing) the last created instance first.
- The actual conversions will be done by using convertToMime() and convertFromMime().
-
- \note The API uses the term "flavor" in some cases. This is for backwards
- compatibility reasons, and should now be understood as UTIs.
-*/
-
-/*
- \enum QMacPasteboardMime::QMacPasteboardMimeType
- \internal
-*/
-
-/*
- Constructs a new conversion object of type \a t, adding it to the
- globally accessed list of available converters.
-*/
-QMacInternalPasteboardMime::QMacInternalPasteboardMime(char t) : type(t)
-{
- qt_mac_addToGlobalMimeList(this);
-}
-
-/*
- Destroys a conversion object, removing it from the global
- list of available converters.
-*/
-QMacInternalPasteboardMime::~QMacInternalPasteboardMime()
-{
- qt_mac_removeFromGlobalMimeList(this);
-}
-
-/*
- Returns the item count for the given \a mimeData
-*/
-int QMacInternalPasteboardMime::count(QMimeData *mimeData)
-{
- Q_UNUSED(mimeData);
- return 1;
-}
-
-class QMacPasteboardMimeAny : public QMacInternalPasteboardMime {
-private:
-
-public:
- QMacPasteboardMimeAny() : QMacInternalPasteboardMime(MIME_QT_CONVERTOR|MIME_ALL) {
- }
- ~QMacPasteboardMimeAny() {
- }
- QString convertorName();
-
- QString flavorFor(const QString &mime);
- QString mimeFor(QString flav);
- bool canConvert(const QString &mime, QString flav);
- QVariant convertToMime(const QString &mime, QList<QByteArray> data, QString flav);
- QList<QByteArray> convertFromMime(const QString &mime, QVariant data, QString flav);
-};
-
-QString QMacPasteboardMimeAny::convertorName()
-{
- return "Any-Mime"_L1;
-}
-
-QString QMacPasteboardMimeAny::flavorFor(const QString &mime)
-{
- // do not handle the mime type name in the drag pasteboard
- if (mime == "application/x-qt-mime-type-name"_L1)
- return QString();
- QString ret = "com.trolltech.anymime."_L1 + mime;
- return ret.replace(u'/', "--"_L1);
-}
-
-QString QMacPasteboardMimeAny::mimeFor(QString flav)
-{
- const QString any_prefix = "com.trolltech.anymime."_L1;
- if (flav.size() > any_prefix.length() && flav.startsWith(any_prefix))
- return flav.mid(any_prefix.length()).replace("--"_L1, "/"_L1);
- return QString();
-}
-
-bool QMacPasteboardMimeAny::canConvert(const QString &mime, QString flav)
-{
- return mimeFor(flav) == mime;
-}
-
-QVariant QMacPasteboardMimeAny::convertToMime(const QString &mime, QList<QByteArray> data, QString)
-{
- if (data.count() > 1)
- qWarning("QMacPasteboardMimeAny: Cannot handle multiple member data");
- QVariant ret;
- if (mime == "text/plain"_L1)
- ret = QString::fromUtf8(data.first());
- else
- ret = data.first();
- return ret;
-}
-
-QList<QByteArray> QMacPasteboardMimeAny::convertFromMime(const QString &mime, QVariant data, QString)
-{
- QList<QByteArray> ret;
- if (mime == "text/plain"_L1)
- ret.append(data.toString().toUtf8());
- else
- ret.append(data.toByteArray());
- return ret;
-}
-
-class QMacPasteboardMimeTypeName : public QMacInternalPasteboardMime {
-private:
-
-public:
- QMacPasteboardMimeTypeName() : QMacInternalPasteboardMime(MIME_QT_CONVERTOR|MIME_ALL) {
- }
- ~QMacPasteboardMimeTypeName() {
- }
- QString convertorName();
-
- QString flavorFor(const QString &mime);
- QString mimeFor(QString flav);
- bool canConvert(const QString &mime, QString flav);
- QVariant convertToMime(const QString &mime, QList<QByteArray> data, QString flav);
- QList<QByteArray> convertFromMime(const QString &mime, QVariant data, QString flav);
-};
-
-QString QMacPasteboardMimeTypeName::convertorName()
-{
- return "Qt-Mime-Type"_L1;
-}
-
-QString QMacPasteboardMimeTypeName::flavorFor(const QString &mime)
-{
- if (mime == "application/x-qt-mime-type-name"_L1)
- return "com.trolltech.qt.MimeTypeName"_L1;
- return QString();
-}
-
-QString QMacPasteboardMimeTypeName::mimeFor(QString)
-{
- return QString();
-}
-
-bool QMacPasteboardMimeTypeName::canConvert(const QString &, QString)
-{
- return false;
-}
-
-QVariant QMacPasteboardMimeTypeName::convertToMime(const QString &, QList<QByteArray>, QString)
-{
- QVariant ret;
- return ret;
-}
-
-QList<QByteArray> QMacPasteboardMimeTypeName::convertFromMime(const QString &, QVariant, QString)
-{
- QList<QByteArray> ret;
- ret.append(QString("x-qt-mime-type-name"_L1).toUtf8());
- return ret;
-}
-
-class QMacPasteboardMimePlainTextFallback : public QMacInternalPasteboardMime {
-public:
- QMacPasteboardMimePlainTextFallback() : QMacInternalPasteboardMime(MIME_ALL) { }
- QString convertorName();
-
- QString flavorFor(const QString &mime);
- QString mimeFor(QString flav);
- bool canConvert(const QString &mime, QString flav);
- QVariant convertToMime(const QString &mime, QList<QByteArray> data, QString flav);
- QList<QByteArray> convertFromMime(const QString &mime, QVariant data, QString flav);
-};
-
-QString QMacPasteboardMimePlainTextFallback::convertorName()
-{
- return "PlainText (public.text)"_L1;
-}
-
-QString QMacPasteboardMimePlainTextFallback::flavorFor(const QString &mime)
-{
- if (mime == "text/plain"_L1)
- return "public.text"_L1;
- return QString();
-}
-
-QString QMacPasteboardMimePlainTextFallback::mimeFor(QString flav)
-{
- if (flav == "public.text"_L1)
- return "text/plain"_L1;
- return QString();
-}
-
-bool QMacPasteboardMimePlainTextFallback::canConvert(const QString &mime, QString flav)
-{
- return mime == mimeFor(flav);
-}
-
-QVariant QMacPasteboardMimePlainTextFallback::convertToMime(const QString &mimetype, QList<QByteArray> data, QString flavor)
-{
- if (data.count() > 1)
- qWarning("QMacPasteboardMimePlainTextFallback: Cannot handle multiple member data");
-
- if (flavor == "public.text"_L1) {
- // Note that public.text is documented by Apple to have an undefined encoding. From
- // testing it seems that utf8 is normally used, at least by Safari on iOS.
- const QByteArray &firstData = data.first();
- return QString(QCFString(CFStringCreateWithBytes(kCFAllocatorDefault,
- reinterpret_cast<const UInt8 *>(firstData.constData()),
- firstData.size(), kCFStringEncodingUTF8, false)));
- } else {
- qWarning("QMime::convertToMime: unhandled mimetype: %s", qPrintable(mimetype));
- }
- return QVariant();
-}
-
-QList<QByteArray> QMacPasteboardMimePlainTextFallback::convertFromMime(const QString &, QVariant data, QString flavor)
-{
- QList<QByteArray> ret;
- QString string = data.toString();
- if (flavor == "public.text"_L1)
- ret.append(string.toUtf8());
- return ret;
-}
-
-class QMacPasteboardMimeUnicodeText : public QMacInternalPasteboardMime {
-public:
- QMacPasteboardMimeUnicodeText() : QMacInternalPasteboardMime(MIME_ALL) { }
- QString convertorName();
-
- QString flavorFor(const QString &mime);
- QString mimeFor(QString flav);
- bool canConvert(const QString &mime, QString flav);
- QVariant convertToMime(const QString &mime, QList<QByteArray> data, QString flav);
- QList<QByteArray> convertFromMime(const QString &mime, QVariant data, QString flav);
-};
-
-QString QMacPasteboardMimeUnicodeText::convertorName()
-{
- return "UnicodeText"_L1;
-}
-
-QString QMacPasteboardMimeUnicodeText::flavorFor(const QString &mime)
-{
- if (mime == "text/plain"_L1)
- return "public.utf16-plain-text"_L1;
- int i = mime.indexOf("charset="_L1);
- if (i >= 0) {
- QString cs(mime.mid(i+8).toLower());
- i = cs.indexOf(u';');
- if (i>=0)
- cs = cs.left(i);
- if (cs == "system"_L1)
- return "public.utf8-plain-text"_L1;
- else if (cs == "iso-10646-ucs-2"_L1 || cs == "utf16"_L1)
- return "public.utf16-plain-text"_L1;
- }
- return QString();
-}
-
-QString QMacPasteboardMimeUnicodeText::mimeFor(QString flav)
-{
- if (flav == "public.utf16-plain-text"_L1 || flav == "public.utf8-plain-text"_L1)
- return "text/plain"_L1;
- return QString();
-}
-
-bool QMacPasteboardMimeUnicodeText::canConvert(const QString &mime, QString flav)
-{
- return (mime == "text/plain"_L1
- && (flav == "public.utf8-plain-text"_L1 || (flav == "public.utf16-plain-text"_L1)));
-}
-
-QVariant QMacPasteboardMimeUnicodeText::convertToMime(const QString &mimetype, QList<QByteArray> data, QString flavor)
-{
- if (data.count() > 1)
- qWarning("QMacPasteboardMimeUnicodeText: Cannot handle multiple member data");
- const QByteArray &firstData = data.first();
- // I can only handle two types (system and unicode) so deal with them that way
- QVariant ret;
- if (flavor == "public.utf8-plain-text"_L1) {
- ret = QString::fromUtf8(firstData);
- } else if (flavor == "public.utf16-plain-text"_L1) {
- QString str = QStringDecoder(QStringDecoder::Utf16)(firstData);
- ret = str;
- } else {
- qWarning("QMime::convertToMime: unhandled mimetype: %s", qPrintable(mimetype));
- }
- return ret;
-}
-
-QList<QByteArray> QMacPasteboardMimeUnicodeText::convertFromMime(const QString &, QVariant data, QString flavor)
-{
- QList<QByteArray> ret;
- QString string = data.toString();
- if (flavor == "public.utf8-plain-text"_L1)
- ret.append(string.toUtf8());
- else if (flavor == "public.utf16-plain-text"_L1) {
- QStringEncoder::Flags f;
-#if defined(Q_OS_MACOS)
- // Some applications such as Microsoft Excel, don't deal well with
- // a BOM present, so we follow the traditional approach of Qt on
- // macOS to not generate public.utf16-plain-text with a BOM.
- f = QStringEncoder::Flag::Default;
-#else
- // Whereas iOS applications will fail to paste if we do _not_
- // include a BOM in the public.utf16-plain-text content, most
- // likely due to converting the data using NSUTF16StringEncoding
- // which assumes big-endian byte order if there is no BOM.
- f = QStringEncoder::Flag::WriteBom;
-#endif
- QStringEncoder encoder(QStringEncoder::Utf16, f);
- ret.append(encoder(string));
- }
- return ret;
-}
-
-class QMacPasteboardMimeHTMLText : public QMacInternalPasteboardMime {
-public:
- QMacPasteboardMimeHTMLText() : QMacInternalPasteboardMime(MIME_ALL) { }
- QString convertorName();
-
- QString flavorFor(const QString &mime);
- QString mimeFor(QString flav);
- bool canConvert(const QString &mime, QString flav);
- QVariant convertToMime(const QString &mime, QList<QByteArray> data, QString flav);
- QList<QByteArray> convertFromMime(const QString &mime, QVariant data, QString flav);
-};
-
-QString QMacPasteboardMimeHTMLText::convertorName()
-{
- return "HTML"_L1;
-}
-
-QString QMacPasteboardMimeHTMLText::flavorFor(const QString &mime)
-{
- if (mime == "text/html"_L1)
- return "public.html"_L1;
- return QString();
-}
-
-QString QMacPasteboardMimeHTMLText::mimeFor(QString flav)
-{
- if (flav == "public.html"_L1)
- return "text/html"_L1;
- return QString();
-}
-
-bool QMacPasteboardMimeHTMLText::canConvert(const QString &mime, QString flav)
-{
- return flavorFor(mime) == flav;
-}
-
-QVariant QMacPasteboardMimeHTMLText::convertToMime(const QString &mimeType, QList<QByteArray> data, QString flavor)
-{
- if (!canConvert(mimeType, flavor))
- return QVariant();
- if (data.count() > 1)
- qWarning("QMacPasteboardMimeHTMLText: Cannot handle multiple member data");
- return data.first();
-}
-
-QList<QByteArray> QMacPasteboardMimeHTMLText::convertFromMime(const QString &mime, QVariant data, QString flavor)
-{
- QList<QByteArray> ret;
- if (!canConvert(mime, flavor))
- return ret;
- ret.append(data.toByteArray());
- return ret;
-}
-
-class QMacPasteboardMimeRtfText : public QMacInternalPasteboardMime {
-public:
- QMacPasteboardMimeRtfText() : QMacInternalPasteboardMime(MIME_ALL) { }
- QString convertorName();
-
- QString flavorFor(const QString &mime);
- QString mimeFor(QString flav);
- bool canConvert(const QString &mime, QString flav);
- QVariant convertToMime(const QString &mime, QList<QByteArray> data, QString flav);
- QList<QByteArray> convertFromMime(const QString &mime, QVariant data, QString flav);
-};
-
-QString QMacPasteboardMimeRtfText::convertorName()
-{
- return "Rtf"_L1;
-}
-
-QString QMacPasteboardMimeRtfText::flavorFor(const QString &mime)
-{
- if (mime == "text/html"_L1)
- return "public.rtf"_L1;
- return QString();
-}
-
-QString QMacPasteboardMimeRtfText::mimeFor(QString flav)
-{
- if (flav == "public.rtf"_L1)
- return "text/html"_L1;
- return QString();
-}
-
-bool QMacPasteboardMimeRtfText::canConvert(const QString &mime, QString flav)
-{
- return mime == mimeFor(flav);
-}
-
-QVariant QMacPasteboardMimeRtfText::convertToMime(const QString &mimeType, QList<QByteArray> data, QString flavor)
-{
- if (!canConvert(mimeType, flavor))
- return QVariant();
- if (data.count() > 1)
- qWarning("QMacPasteboardMimeHTMLText: Cannot handle multiple member data");
-
- // Read RTF into to NSAttributedString, then convert the string to HTML
- NSAttributedString *string = [[NSAttributedString alloc] initWithData:data.at(0).toNSData()
- options:@{NSDocumentTypeDocumentAttribute: NSRTFTextDocumentType}
- documentAttributes:nil
- error:nil];
-
- NSError *error;
- NSRange range = NSMakeRange(0, [string length]);
- NSDictionary *dict = @{NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType};
- NSData *htmlData = [string dataFromRange:range documentAttributes:dict error:&error];
- return QByteArray::fromNSData(htmlData);
-}
-
-QList<QByteArray> QMacPasteboardMimeRtfText::convertFromMime(const QString &mime, QVariant data, QString flavor)
-{
- QList<QByteArray> ret;
- if (!canConvert(mime, flavor))
- return ret;
-
- NSAttributedString *string = [[NSAttributedString alloc] initWithData:data.toByteArray().toNSData()
- options:@{NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType}
- documentAttributes:nil
- error:nil];
-
- NSError *error;
- NSRange range = NSMakeRange(0, [string length]);
- NSDictionary *dict = @{NSDocumentTypeDocumentAttribute: NSRTFTextDocumentType};
- NSData *rtfData = [string dataFromRange:range documentAttributes:dict error:&error];
- ret << QByteArray::fromNSData(rtfData);
- return ret;
-}
-
-class QMacPasteboardMimeFileUri : public QMacInternalPasteboardMime {
-public:
- QMacPasteboardMimeFileUri() : QMacInternalPasteboardMime(MIME_ALL) { }
- QString convertorName();
-
- QString flavorFor(const QString &mime);
- QString mimeFor(QString flav);
- bool canConvert(const QString &mime, QString flav);
- QVariant convertToMime(const QString &mime, QList<QByteArray> data, QString flav);
- QList<QByteArray> convertFromMime(const QString &mime, QVariant data, QString flav);
- int count(QMimeData *mimeData);
-};
-
-QString QMacPasteboardMimeFileUri::convertorName()
-{
- return "FileURL"_L1;
-}
-
-QString QMacPasteboardMimeFileUri::flavorFor(const QString &mime)
-{
- if (mime == "text/uri-list"_L1)
- return "public.file-url"_L1;
- return QString();
-}
-
-QString QMacPasteboardMimeFileUri::mimeFor(QString flav)
-{
- if (flav == "public.file-url"_L1)
- return "text/uri-list"_L1;
- return QString();
-}
-
-bool QMacPasteboardMimeFileUri::canConvert(const QString &mime, QString flav)
-{
- return mime == "text/uri-list"_L1 && flav == "public.file-url"_L1;
-}
-
-QVariant QMacPasteboardMimeFileUri::convertToMime(const QString &mime, QList<QByteArray> data, QString flav)
-{
- if (!canConvert(mime, flav))
- return QVariant();
- QList<QVariant> ret;
- for (int i = 0; i < data.size(); ++i) {
- const QByteArray &a = data.at(i);
- NSString *urlString = [[[NSString alloc] initWithBytesNoCopy:(void *)a.data() length:a.size()
- encoding:NSUTF8StringEncoding freeWhenDone:NO] autorelease];
- NSURL *nsurl = [NSURL URLWithString:urlString];
- QUrl url;
- // OS X 10.10 sends file references instead of file paths
- if ([nsurl isFileReferenceURL]) {
- url = QUrl::fromNSURL([nsurl filePathURL]);
- } else {
- url = QUrl::fromNSURL(nsurl);
- }
-
- if (url.host().toLower() == "localhost"_L1)
- url.setHost(QString());
-
- url.setPath(url.path().normalized(QString::NormalizationForm_C));
- ret.append(url);
- }
- return QVariant(ret);
-}
-
-QList<QByteArray> QMacPasteboardMimeFileUri::convertFromMime(const QString &mime, QVariant data, QString flav)
-{
- QList<QByteArray> ret;
- if (!canConvert(mime, flav))
- return ret;
- QList<QVariant> urls = data.toList();
- for (int i = 0; i < urls.size(); ++i) {
- QUrl url = urls.at(i).toUrl();
- if (url.scheme().isEmpty())
- url.setScheme("file"_L1);
- if (url.scheme() == "file"_L1) {
- if (url.host().isEmpty())
- url.setHost("localhost"_L1);
- url.setPath(url.path().normalized(QString::NormalizationForm_D));
- }
- if (url.isLocalFile())
- ret.append(url.toEncoded());
- }
- return ret;
-}
-
-int QMacPasteboardMimeFileUri::count(QMimeData *mimeData)
-{
- return mimeData->urls().count();
-}
-
-class QMacPasteboardMimeUrl : public QMacInternalPasteboardMime {
-public:
- QMacPasteboardMimeUrl() : QMacInternalPasteboardMime(MIME_ALL) { }
- QString convertorName();
-
- QString flavorFor(const QString &mime);
- QString mimeFor(QString flav);
- bool canConvert(const QString &mime, QString flav);
- QVariant convertToMime(const QString &mime, QList<QByteArray> data, QString flav);
- QList<QByteArray> convertFromMime(const QString &mime, QVariant data, QString flav);
-};
-
-QString QMacPasteboardMimeUrl::convertorName()
-{
- return "URL"_L1;
-}
-
-QString QMacPasteboardMimeUrl::flavorFor(const QString &mime)
-{
- if (mime.startsWith("text/uri-list"_L1))
- return "public.url"_L1;
- return QString();
-}
-
-QString QMacPasteboardMimeUrl::mimeFor(QString flav)
-{
- if (flav == "public.url"_L1)
- return "text/uri-list"_L1;
- return QString();
-}
-
-bool QMacPasteboardMimeUrl::canConvert(const QString &mime, QString flav)
-{
- return flav == "public.url"_L1
- && mime == "text/uri-list"_L1;
-}
-
-QVariant QMacPasteboardMimeUrl::convertToMime(const QString &mime, QList<QByteArray> data, QString flav)
-{
- if (!canConvert(mime, flav))
- return QVariant();
-
- QList<QVariant> ret;
- for (int i=0; i<data.size(); ++i) {
- QUrl url = QUrl::fromEncoded(data.at(i));
- if (url.host().toLower() == "localhost"_L1)
- url.setHost(QString());
- url.setPath(url.path().normalized(QString::NormalizationForm_C));
- ret.append(url);
- }
- return QVariant(ret);
-}
-
-QList<QByteArray> QMacPasteboardMimeUrl::convertFromMime(const QString &mime, QVariant data, QString flav)
-{
- QList<QByteArray> ret;
- if (!canConvert(mime, flav))
- return ret;
-
- QList<QVariant> urls = data.toList();
- for (int i=0; i<urls.size(); ++i) {
- QUrl url = urls.at(i).toUrl();
- if (url.scheme().isEmpty())
- url.setScheme("file"_L1);
- if (url.scheme() == "file"_L1) {
- if (url.host().isEmpty())
- url.setHost("localhost"_L1);
- url.setPath(url.path().normalized(QString::NormalizationForm_D));
- }
- ret.append(url.toEncoded());
- }
- return ret;
-}
-
-class QMacPasteboardMimeVCard : public QMacInternalPasteboardMime
-{
-public:
- QMacPasteboardMimeVCard() : QMacInternalPasteboardMime(MIME_ALL){ }
- QString convertorName();
-
- QString flavorFor(const QString &mime);
- QString mimeFor(QString flav);
- bool canConvert(const QString &mime, QString flav);
- QVariant convertToMime(const QString &mime, QList<QByteArray> data, QString flav);
- QList<QByteArray> convertFromMime(const QString &mime, QVariant data, QString flav);
-};
-
-QString QMacPasteboardMimeVCard::convertorName()
-{
- return "VCard"_L1;
-}
-
-bool QMacPasteboardMimeVCard::canConvert(const QString &mime, QString flav)
-{
- return mimeFor(flav) == mime;
-}
-
-QString QMacPasteboardMimeVCard::flavorFor(const QString &mime)
-{
- if (mime.startsWith("text/vcard"_L1))
- return "public.vcard"_L1;
- return QString();
-}
-
-QString QMacPasteboardMimeVCard::mimeFor(QString flav)
-{
- if (flav == "public.vcard"_L1)
- return "text/vcard"_L1;
- return QString();
-}
-
-QVariant QMacPasteboardMimeVCard::convertToMime(const QString &mime, QList<QByteArray> data, QString)
-{
- QByteArray cards;
- if (mime == "text/vcard"_L1) {
- for (int i=0; i<data.size(); ++i)
- cards += data[i];
- }
- return QVariant(cards);
-}
-
-QList<QByteArray> QMacPasteboardMimeVCard::convertFromMime(const QString &mime, QVariant data, QString)
-{
- QList<QByteArray> ret;
- if (mime == "text/vcard"_L1)
- ret.append(data.toString().toUtf8());
- return ret;
-}
-
-extern QImage qt_mac_toQImage(CGImageRef image);
-extern CGImageRef qt_mac_toCGImage(const QImage &qImage);
-
-class QMacPasteboardMimeTiff : public QMacInternalPasteboardMime {
-public:
- QMacPasteboardMimeTiff() : QMacInternalPasteboardMime(MIME_ALL) { }
- QString convertorName();
-
- QString flavorFor(const QString &mime);
- QString mimeFor(QString flav);
- bool canConvert(const QString &mime, QString flav);
- QVariant convertToMime(const QString &mime, QList<QByteArray> data, QString flav);
- QList<QByteArray> convertFromMime(const QString &mime, QVariant data, QString flav);
-};
-
-QString QMacPasteboardMimeTiff::convertorName()
-{
- return "Tiff"_L1;
-}
-
-QString QMacPasteboardMimeTiff::flavorFor(const QString &mime)
-{
- if (mime.startsWith("application/x-qt-image"_L1))
- return "public.tiff"_L1;
- return QString();
-}
-
-QString QMacPasteboardMimeTiff::mimeFor(QString flav)
-{
- if (flav == "public.tiff"_L1)
- return "application/x-qt-image"_L1;
- return QString();
-}
-
-bool QMacPasteboardMimeTiff::canConvert(const QString &mime, QString flav)
-{
- return flav == "public.tiff"_L1 && mime == "application/x-qt-image"_L1;
-}
-
-QVariant QMacPasteboardMimeTiff::convertToMime(const QString &mime, QList<QByteArray> data, QString flav)
-{
- if (data.count() > 1)
- qWarning("QMacPasteboardMimeTiff: Cannot handle multiple member data");
-
- if (!canConvert(mime, flav))
- return QVariant();
-
- QCFType<CFDataRef> tiffData = data.first().toRawCFData();
- QCFType<CGImageSourceRef> imageSource = CGImageSourceCreateWithData(tiffData, 0);
-
- if (QCFType<CGImageRef> image = CGImageSourceCreateImageAtIndex(imageSource, 0, 0))
- return QVariant(qt_mac_toQImage(image));
-
- return QVariant();
-}
-
-QList<QByteArray> QMacPasteboardMimeTiff::convertFromMime(const QString &mime, QVariant variant, QString flav)
-{
- if (!canConvert(mime, flav))
- return QList<QByteArray>();
-
- QCFType<CFMutableDataRef> data = CFDataCreateMutable(0, 0);
- QCFType<CGImageDestinationRef> imageDestination = CGImageDestinationCreateWithData(data, kUTTypeTIFF, 1, 0);
-
- if (!imageDestination)
- return QList<QByteArray>();
-
- QImage img = qvariant_cast<QImage>(variant);
- NSDictionary *props = @{
- static_cast<NSString *>(kCGImagePropertyPixelWidth): @(img.width()),
- static_cast<NSString *>(kCGImagePropertyPixelHeight): @(img.height())
- };
-
- CGImageDestinationAddImage(imageDestination, qt_mac_toCGImage(img), static_cast<CFDictionaryRef>(props));
- CGImageDestinationFinalize(imageDestination);
-
- return QList<QByteArray>() << QByteArray::fromCFData(data);
-}
-
-/*!
- \internal
-
- This is an internal function.
-*/
-void QMacInternalPasteboardMime::initializeMimeTypes()
-{
- if (globalMimeList()->isEmpty()) {
- // Create QMacPasteboardMimeAny first to put it at the end of globalMimeList
- // with lowest priority. (the constructor prepends to the list)
- new QMacPasteboardMimeAny;
-
- //standard types that we wrap
- new QMacPasteboardMimeTiff;
- new QMacPasteboardMimePlainTextFallback;
- new QMacPasteboardMimeUnicodeText;
- new QMacPasteboardMimeRtfText;
- new QMacPasteboardMimeHTMLText;
- new QMacPasteboardMimeFileUri;
- new QMacPasteboardMimeUrl;
- new QMacPasteboardMimeTypeName;
- new QMacPasteboardMimeVCard;
- }
-}
-
-/*!
- \internal
-*/
-void QMacInternalPasteboardMime::destroyMimeTypes()
-{
- MimeList *mimes = globalMimeList();
- while (!mimes->isEmpty())
- delete mimes->takeFirst();
-}
-
-/*
- Returns the most-recently created QMacPasteboardMime of type \a t that can convert
- between the \a mime and \a flav formats. Returns 0 if no such convertor
- exists.
-*/
-QMacInternalPasteboardMime*
-QMacInternalPasteboardMime::convertor(uchar t, const QString &mime, QString flav)
-{
- MimeList *mimes = globalMimeList();
- for (MimeList::const_iterator it = mimes->constBegin(); it != mimes->constEnd(); ++it) {
-#ifdef DEBUG_MIME_MAPS
- qDebug("QMacPasteboardMime::convertor: seeing if %s (%d) can convert %s to %d[%c%c%c%c] [%d]",
- (*it)->convertorName().toLatin1().constData(),
- (*it)->type & t, mime.toLatin1().constData(),
- flav, (flav >> 24) & 0xFF, (flav >> 16) & 0xFF, (flav >> 8) & 0xFF, (flav) & 0xFF,
- (*it)->canConvert(mime,flav));
- for (int i = 0; i < (*it)->countFlavors(); ++i) {
- int f = (*it)->flavor(i);
- qDebug(" %d) %d[%c%c%c%c] [%s]", i, f,
- (f >> 24) & 0xFF, (f >> 16) & 0xFF, (f >> 8) & 0xFF, (f) & 0xFF,
- (*it)->convertorName().toLatin1().constData());
- }
-#endif
- if (((*it)->type & t) && (*it)->canConvert(mime, flav))
- return (*it);
- }
- return 0;
-}
-/*
- Returns a MIME type of type \a t for \a flav, or 0 if none exists.
-*/
-QString QMacInternalPasteboardMime::flavorToMime(uchar t, QString flav)
-{
- MimeList *mimes = globalMimeList();
- for (MimeList::const_iterator it = mimes->constBegin(); it != mimes->constEnd(); ++it) {
-#ifdef DEBUG_MIME_MAPS
- qDebug("QMacMIme::flavorToMime: attempting %s (%d) for flavor %d[%c%c%c%c] [%s]",
- (*it)->convertorName().toLatin1().constData(),
- (*it)->type & t, flav, (flav >> 24) & 0xFF, (flav >> 16) & 0xFF, (flav >> 8) & 0xFF, (flav) & 0xFF,
- (*it)->mimeFor(flav).toLatin1().constData());
-
-#endif
- if ((*it)->type & t) {
- QString mimeType = (*it)->mimeFor(flav);
- if (!mimeType.isNull())
- return mimeType;
- }
- }
- return QString();
-}
-
-/*
- Returns a list of all currently defined QMacPasteboardMime objects of type \a t.
-*/
-QList<QMacInternalPasteboardMime*> QMacInternalPasteboardMime::all(uchar t)
-{
- MimeList ret;
- MimeList *mimes = globalMimeList();
- for (MimeList::const_iterator it = mimes->constBegin(); it != mimes->constEnd(); ++it) {
- if ((*it)->type & t)
- ret.append((*it));
- }
- return ret;
-}
-
-
-/*
- \fn QString QMacPasteboardMime::convertorName()
-
- Returns a name for the convertor.
-
- All subclasses must reimplement this pure virtual function.
-*/
-
-/*
- \fn bool QMacPasteboardMime::canConvert(const QString &mime, QString flav)
-
- Returns \c true if the convertor can convert (both ways) between
- \a mime and \a flav; otherwise returns \c false.
-
- All subclasses must reimplement this pure virtual function.
-*/
-
-/*
- \fn QString QMacPasteboardMime::mimeFor(QString flav)
-
- Returns the MIME UTI used for Mac flavor \a flav, or 0 if this
- convertor does not support \a flav.
-
- All subclasses must reimplement this pure virtual function.
-*/
-
-/*
- \fn QString QMacPasteboardMime::flavorFor(const QString &mime)
-
- Returns the Mac UTI used for MIME type \a mime, or 0 if this
- convertor does not support \a mime.
-
- All subclasses must reimplement this pure virtual function.
-*/
-
-/*
- \fn QVariant QMacPasteboardMime::convertToMime(const QString &mime, QList<QByteArray> data, QString flav)
-
- Returns \a data converted from Mac UTI \a flav to MIME type \a
- mime.
-
- Note that Mac flavors must all be self-terminating. The input \a
- data may contain trailing data.
-
- All subclasses must reimplement this pure virtual function.
-*/
-
-/*
- \fn QList<QByteArray> QMacPasteboardMime::convertFromMime(const QString &mime, QVariant data, QString flav)
-
- Returns \a data converted from MIME type \a mime
- to Mac UTI \a flav.
-
- Note that Mac flavors must all be self-terminating. The return
- value may contain trailing data.
-
- All subclasses must reimplement this pure virtual function.
-*/
-
-QT_END_NAMESPACE
diff --git a/src/gui/platform/darwin/qmacmime_p.h b/src/gui/platform/darwin/qmacmime_p.h
deleted file mode 100644
index bd926f59fe..0000000000
--- a/src/gui/platform/darwin/qmacmime_p.h
+++ /dev/null
@@ -1,63 +0,0 @@
-// Copyright (C) 2016 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
-
-#ifndef QMACMIME_H
-#define QMACMIME_H
-
-//
-// W A R N I N G
-// -------------
-//
-// This file is not part of the Qt API. It exists purely as an
-// implementation detail. This header file may change from version to
-// version without notice, or even be removed.
-//
-// We mean it.
-//
-
-
-#include <QtGui/private/qtguiglobal_p.h>
-
-#include <CoreFoundation/CoreFoundation.h>
-
-QT_BEGIN_NAMESPACE
-
-// Duplicate of QMacPasteboardMime in QtMacExtras. Keep in sync!
-class Q_GUI_EXPORT QMacInternalPasteboardMime {
- char type;
-public:
- enum QMacPasteboardMimeType { MIME_DND=0x01,
- MIME_CLIP=0x02,
- MIME_QT_CONVERTOR=0x04,
- MIME_QT3_CONVERTOR=0x08,
- MIME_ALL=MIME_DND|MIME_CLIP
- };
- explicit QMacInternalPasteboardMime(char);
- virtual ~QMacInternalPasteboardMime();
-
- static void initializeMimeTypes();
- static void destroyMimeTypes();
-
- static QList<QMacInternalPasteboardMime*> all(uchar);
- static QMacInternalPasteboardMime *convertor(uchar, const QString &mime, QString flav);
- static QString flavorToMime(uchar, QString flav);
-
- virtual QString convertorName() = 0;
-
- virtual bool canConvert(const QString &mime, QString flav) = 0;
- virtual QString mimeFor(QString flav) = 0;
- virtual QString flavorFor(const QString &mime) = 0;
- virtual QVariant convertToMime(const QString &mime, QList<QByteArray> data, QString flav) = 0;
- virtual QList<QByteArray> convertFromMime(const QString &mime, QVariant data, QString flav) = 0;
- virtual int count(QMimeData *mimeData);
-};
-
-Q_GUI_EXPORT void qt_mac_addToGlobalMimeList(QMacInternalPasteboardMime *macMime);
-Q_GUI_EXPORT void qt_mac_removeFromGlobalMimeList(QMacInternalPasteboardMime *macMime);
-Q_GUI_EXPORT void qt_mac_registerDraggedTypes(const QStringList &types);
-Q_GUI_EXPORT const QStringList& qt_mac_enabledDraggedTypes();
-
-QT_END_NAMESPACE
-
-#endif
-
diff --git a/src/gui/platform/darwin/qmacmimeregistry.mm b/src/gui/platform/darwin/qmacmimeregistry.mm
new file mode 100644
index 0000000000..6710a0656f
--- /dev/null
+++ b/src/gui/platform/darwin/qmacmimeregistry.mm
@@ -0,0 +1,118 @@
+// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include <QtCore/qmimedata.h>
+
+#include "qutimimeconverter.h"
+#include "qmacmimeregistry_p.h"
+#include "qguiapplication.h"
+#include "private/qcore_mac_p.h"
+
+QT_BEGIN_NAMESPACE
+
+using namespace Qt::StringLiterals;
+
+namespace QMacMimeRegistry {
+
+typedef QList<QUtiMimeConverter*> MimeList;
+Q_GLOBAL_STATIC(MimeList, globalMimeList)
+Q_GLOBAL_STATIC(QStringList, globalDraggedTypesList)
+
+// implemented in qutimimeconverter.mm
+void registerBuiltInTypes();
+
+void registerDraggedTypes(const QStringList &types)
+{
+ (*globalDraggedTypesList()) += types;
+}
+
+const QStringList& enabledDraggedTypes()
+{
+ return (*globalDraggedTypesList());
+}
+
+/*****************************************************************************
+ QDnD debug facilities
+ *****************************************************************************/
+//#define DEBUG_MIME_MAPS
+
+/*!
+ \class QMacMimeRegistry
+ \internal
+ \ingroup draganddrop
+*/
+
+/*!
+ \internal
+
+ This is an internal function.
+*/
+void initializeMimeTypes()
+{
+ if (globalMimeList()->isEmpty())
+ registerBuiltInTypes();
+}
+
+/*!
+ \internal
+*/
+void destroyMimeTypes()
+{
+ MimeList *mimes = globalMimeList();
+ while (!mimes->isEmpty())
+ delete mimes->takeFirst();
+}
+
+/*
+ Returns a MIME type of for scope \a scope for \a uti, or \nullptr if none exists.
+*/
+QString flavorToMime(QUtiMimeConverter::HandlerScope scope, const QString &uti)
+{
+ const MimeList &mimes = *globalMimeList();
+ for (const auto &mime : mimes) {
+ const bool relevantScope = mime->scope() & scope;
+#ifdef DEBUG_MIME_MAPS
+ qDebug("QMacMimeRegistry::flavorToMime: attempting (%d) for uti %s [%s]",
+ relevantScope, qPrintable(uti), qPrintable((*it)->mimeForUti(uti)));
+#endif
+ if (relevantScope) {
+ const QString mimeType = mime->mimeForUti(uti);
+ if (!mimeType.isNull())
+ return mimeType;
+ }
+ }
+ return QString();
+}
+
+void registerMimeConverter(QUtiMimeConverter *macMime)
+{
+ // globalMimeList is in decreasing priority order. Recently added
+ // converters take prioity over previously added converters: prepend
+ // to the list.
+ globalMimeList()->prepend(macMime);
+}
+
+void unregisterMimeConverter(QUtiMimeConverter *macMime)
+{
+ if (!QGuiApplication::closingDown())
+ globalMimeList()->removeAll(macMime);
+}
+
+
+/*
+ Returns a list of all currently defined QUtiMimeConverter objects for scope \a scope.
+*/
+QList<QUtiMimeConverter *> all(QUtiMimeConverter::HandlerScope scope)
+{
+ MimeList ret;
+ const MimeList &mimes = *globalMimeList();
+ for (const auto &mime : mimes) {
+ if (mime->scope() & scope)
+ ret.append(mime);
+ }
+ return ret;
+}
+
+} // namespace QMacMimeRegistry
+
+QT_END_NAMESPACE
diff --git a/src/gui/platform/darwin/qmacmimeregistry_p.h b/src/gui/platform/darwin/qmacmimeregistry_p.h
new file mode 100644
index 0000000000..5928b81959
--- /dev/null
+++ b/src/gui/platform/darwin/qmacmimeregistry_p.h
@@ -0,0 +1,42 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef QMACMIMEREGISTRY_H
+#define QMACMIMEREGISTRY_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+
+#include <QtGui/private/qtguiglobal_p.h>
+#include <QtGui/qutimimeconverter.h>
+
+#include <CoreFoundation/CoreFoundation.h>
+
+QT_BEGIN_NAMESPACE
+
+namespace QMacMimeRegistry {
+ Q_GUI_EXPORT void initializeMimeTypes();
+ Q_GUI_EXPORT void destroyMimeTypes();
+
+ Q_GUI_EXPORT void registerMimeConverter(QUtiMimeConverter *);
+ Q_GUI_EXPORT void unregisterMimeConverter(QUtiMimeConverter *);
+
+ Q_GUI_EXPORT QList<QUtiMimeConverter *> all(QUtiMimeConverter::HandlerScope scope);
+ Q_GUI_EXPORT QString flavorToMime(QUtiMimeConverter::HandlerScope scope, const QString &flav);
+
+ Q_GUI_EXPORT void registerDraggedTypes(const QStringList &types);
+ Q_GUI_EXPORT const QStringList& enabledDraggedTypes();
+};
+
+QT_END_NAMESPACE
+
+#endif // QMACMIMEREGISTRY_H
diff --git a/src/gui/platform/darwin/qutimimeconverter.h b/src/gui/platform/darwin/qutimimeconverter.h
new file mode 100644
index 0000000000..e9297b5fa0
--- /dev/null
+++ b/src/gui/platform/darwin/qutimimeconverter.h
@@ -0,0 +1,62 @@
+// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef QUTIMIMECONVERTER_H
+#define QUTIMIMECONVERTER_H
+
+#include <QtGui/qtguiglobal.h>
+
+#include <QtCore/qlist.h>
+
+QT_BEGIN_NAMESPACE
+
+class QByteArray;
+class QString;
+class QVariant;
+class QMimeData;
+
+class Q_GUI_EXPORT QUtiMimeConverter
+{
+ Q_DISABLE_COPY(QUtiMimeConverter)
+public:
+ enum class HandlerScopeFlag : uint8_t
+ {
+ DnD = 0x01,
+ Clipboard = 0x02,
+ Qt_compatible = 0x04,
+ Qt3_compatible = 0x08,
+ All = DnD|Clipboard,
+ AllCompatible = All|Qt_compatible
+ };
+ Q_DECLARE_FLAGS(HandlerScope, HandlerScopeFlag)
+
+ QUtiMimeConverter();
+ virtual ~QUtiMimeConverter();
+
+ HandlerScope scope() const { return m_scope; }
+ bool canConvert(const QString &mime, const QString &uti) const { return mimeForUti(uti) == mime; }
+
+ // for converting from Qt
+ virtual QList<QByteArray> convertFromMime(const QString &mime, const QVariant &data, const QString &uti) const = 0;
+ virtual QString utiForMime(const QString &mime) const = 0;
+
+ // for converting to Qt
+ virtual QString mimeForUti(const QString &uti) const = 0;
+ virtual QVariant convertToMime(const QString &mime, const QList<QByteArray> &data, const QString &uti) const = 0;
+ virtual int count(const QMimeData *mimeData) const;
+
+private:
+ friend class QMacMimeTypeName;
+ friend class QMacMimeAny;
+
+ explicit QUtiMimeConverter(HandlerScope scope);
+
+ const HandlerScope m_scope;
+};
+Q_DECLARE_OPERATORS_FOR_FLAGS(QUtiMimeConverter::HandlerScope)
+
+
+QT_END_NAMESPACE
+
+#endif
+
diff --git a/src/gui/platform/darwin/qutimimeconverter.mm b/src/gui/platform/darwin/qutimimeconverter.mm
new file mode 100644
index 0000000000..ee643fd0c6
--- /dev/null
+++ b/src/gui/platform/darwin/qutimimeconverter.mm
@@ -0,0 +1,823 @@
+// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include <ImageIO/ImageIO.h>
+#include <CoreFoundation/CoreFoundation.h>
+#include <UniformTypeIdentifiers/UTCoreTypes.h>
+
+#include <QtCore/qsystemdetection.h>
+#include <QtCore/qurl.h>
+#include <QtGui/qimage.h>
+#include <QtCore/qmimedata.h>
+#include <QtCore/qstringconverter.h>
+
+#if defined(Q_OS_MACOS)
+#import <AppKit/AppKit.h>
+#else
+#include <MobileCoreServices/MobileCoreServices.h>
+#endif
+
+#if defined(QT_PLATFORM_UIKIT)
+#import <UIKit/UIKit.h>
+#endif
+
+#include "qutimimeconverter.h"
+#include "qmacmimeregistry_p.h"
+#include "qguiapplication.h"
+#include "private/qcore_mac_p.h"
+
+QT_BEGIN_NAMESPACE
+
+using namespace Qt::StringLiterals;
+
+/*****************************************************************************
+ QDnD debug facilities
+ *****************************************************************************/
+//#define DEBUG_MIME_MAPS
+
+/*!
+ \class QUtiMimeConverter
+ \brief The QUtiMimeConverter class converts between a MIME type and a
+ \l{https://developer.apple.com/documentation/uniformtypeidentifiers}
+ {Uniform Type Identifier (UTI)} format.
+ \since 6.5
+
+ \ingroup draganddrop
+ \inmodule QtGui
+
+ Qt's drag and drop and clipboard facilities use the MIME
+ standard. On X11, this maps trivially to the Xdnd protocol. On
+ Mac, although some applications use MIME to describe clipboard
+ contents, it is more common to use Apple's UTI format.
+
+ QUtiMimeConverter's role is to bridge the gap between MIME and UTI;
+ By subclasses this class, one can extend Qt's drag and drop
+ and clipboard handling to convert to and from unsupported, or proprietary, UTI formats.
+
+ Construct an instance of your converter implementation after instantiating
+ QGuiApplication:
+
+ \code
+ int main(int argc, char **argv)
+ {
+ QGuiApplication app(argc, argv);
+ JsonMimeConverter jsonConverter;
+ }
+ \endcode
+
+ Destroying the instance will unregister the converter and remove support
+ for the conversion. It is also valid to heap-allocate the converter
+ instance; Qt takes ownership and will delete the converter object during
+ QGuiApplication shut-down.
+
+ Qt has predefined support for the following UTIs:
+ \list
+ \li public.utf8-plain-text - converts to "text/plain"
+ \li public.utf16-plain-text - converts to "text/plain"
+ \li public.text - converts to "text/plain"
+ \li public.html - converts to "text/html"
+ \li public.url - converts to "text/uri-list"
+ \li public.file-url - converts to "text/uri-list"
+ \li public.tiff - converts to "application/x-qt-image"
+ \li public.vcard - converts to "text/plain"
+ \li com.apple.traditional-mac-plain-text - converts to "text/plain"
+ \li com.apple.pict - converts to "application/x-qt-image"
+ \endlist
+
+ When working with MIME data, Qt will iterate through all instances of QUtiMimeConverter to find
+ find an instance that can convert to, or from, a specific MIME type. It will do this by calling
+ mimeForUti() or utiForMime() on each instance, starting with (and choosing) the last created
+ instance first. The actual conversions will be done by using convertToMime() and convertFromMime().
+*/
+
+/*!
+ \enum QUtiMimeConverter::HandlerScope
+ \internal
+*/
+
+/*!
+ \internal
+ Constructs a new conversion object of type \a scope, adding it to the
+ globally accessed list of available converters.
+*/
+QUtiMimeConverter::QUtiMimeConverter(HandlerScope scope)
+ : m_scope(scope)
+{
+ QMacMimeRegistry::registerMimeConverter(this);
+}
+
+/*!
+ Constructs a new conversion object and adds it to the
+ globally accessed list of available converters.
+
+ Call this constructor after QGuiApplication has been created.
+*/
+QUtiMimeConverter::QUtiMimeConverter()
+ : QUtiMimeConverter(HandlerScopeFlag::All)
+{
+}
+
+/*!
+ Destroys a conversion object, removing it from the global
+ list of available converters.
+*/
+QUtiMimeConverter::~QUtiMimeConverter()
+{
+ QMacMimeRegistry::unregisterMimeConverter(this);
+}
+
+/*!
+ Returns the item count for the given \a mimeData
+*/
+int QUtiMimeConverter::count(const QMimeData *mimeData) const
+{
+ Q_UNUSED(mimeData);
+ return 1;
+}
+
+/*!
+ \fn bool QUtiMimeConverter::canConvert(const QString &mime, const QString &uti) const
+
+ Returns \c true if the converter can convert (both ways) between
+ \a mime and \a uti; otherwise returns \c false.
+*/
+
+/*!
+ \fn QString QUtiMimeConverter::mimeForUti(const QString &uti) const
+
+ Returns the MIME type used for Mac UTI \a uti, or an empty string if
+ this converter does not support converting from \a uti.
+
+ All subclasses must reimplement this pure virtual function.
+*/
+
+/*!
+ \fn QString QUtiMimeConverter::utiForMime(const QString &mime) const
+
+ Returns the Mac UTI used for MIME type \a mime, or an empty string if
+ this converter does not support converting from \a mime.
+
+ All subclasses must reimplement this pure virtual function.
+*/
+
+/*!
+ \fn QVariant QUtiMimeConverter::convertToMime(const QString &mime,
+ const QList<QByteArray> &data, const QString &uti) const
+
+ Returns \a data converted from Mac UTI \a uti to MIME type \a mime.
+
+ Note that Mac UTIs must all be self-terminating. The input \a data
+ may contain trailing data.
+
+ All subclasses must reimplement this pure virtual function.
+*/
+
+/*!
+ \fn QList<QByteArray> QUtiMimeConverter::convertFromMime(const QString &mime,
+ const QVariant &data, const QString & uti) const
+
+ Returns \a data converted from MIME type \a mime to Mac UTI \a uti.
+
+ Note that Mac UTIs must all be self-terminating. The return
+ value may contain trailing data.
+
+ All subclasses must reimplement this pure virtual function.
+*/
+
+
+class QMacMimeAny : public QUtiMimeConverter {
+public:
+ QMacMimeAny() : QUtiMimeConverter(HandlerScopeFlag::AllCompatible) {}
+
+ QString utiForMime(const QString &mime) const override;
+ QString mimeForUti(const QString &uti) const override;
+ QVariant convertToMime(const QString &mime, const QList<QByteArray> &data,
+ const QString &uti) const override;
+ QList<QByteArray> convertFromMime(const QString &mime, const QVariant &data,
+ const QString &uti) const override;
+};
+
+QString QMacMimeAny::utiForMime(const QString &mime) const
+{
+ // do not handle the mime type name in the drag pasteboard
+ if (mime == "application/x-qt-mime-type-name"_L1)
+ return QString();
+ QString ret = "com.trolltech.anymime."_L1 + mime;
+ return ret.replace(u'/', "--"_L1);
+}
+
+QString QMacMimeAny::mimeForUti(const QString &uti) const
+{
+ const QString any_prefix = "com.trolltech.anymime."_L1;
+ if (uti.size() > any_prefix.length() && uti.startsWith(any_prefix))
+ return uti.mid(any_prefix.length()).replace("--"_L1, "/"_L1);
+ return QString();
+}
+
+QVariant QMacMimeAny::convertToMime(const QString &mime, const QList<QByteArray> &data,
+ const QString &) const
+{
+ if (data.count() > 1)
+ qWarning("QMacMimeAny: Cannot handle multiple member data");
+ QVariant ret;
+ if (mime == "text/plain"_L1)
+ ret = QString::fromUtf8(data.first());
+ else
+ ret = data.first();
+ return ret;
+}
+
+QList<QByteArray> QMacMimeAny::convertFromMime(const QString &mime, const QVariant &data,
+ const QString &) const
+{
+ QList<QByteArray> ret;
+ if (mime == "text/plain"_L1)
+ ret.append(data.toString().toUtf8());
+ else
+ ret.append(data.toByteArray());
+ return ret;
+}
+
+class QMacMimeTypeName : public QUtiMimeConverter {
+private:
+
+public:
+ QMacMimeTypeName(): QUtiMimeConverter(HandlerScopeFlag::AllCompatible) {}
+
+ QString utiForMime(const QString &mime) const override;
+ QString mimeForUti(const QString &uti) const override;
+ QVariant convertToMime(const QString &mime, const QList<QByteArray> &data, const QString &uti) const override;
+ QList<QByteArray> convertFromMime(const QString &mime, const QVariant &data, const QString &uti) const override;
+};
+
+QString QMacMimeTypeName::utiForMime(const QString &mime) const
+{
+ if (mime == "application/x-qt-mime-type-name"_L1)
+ return u"com.trolltech.qt.MimeTypeName"_s;
+ return QString();
+}
+
+QString QMacMimeTypeName::mimeForUti(const QString &) const
+{
+ return QString();
+}
+
+QVariant QMacMimeTypeName::convertToMime(const QString &, const QList<QByteArray> &, const QString &) const
+{
+ QVariant ret;
+ return ret;
+}
+
+QList<QByteArray> QMacMimeTypeName::convertFromMime(const QString &, const QVariant &, const QString &) const
+{
+ QList<QByteArray> ret;
+ ret.append(QString("x-qt-mime-type-name"_L1).toUtf8());
+ return ret;
+}
+
+class QMacMimePlainTextFallback : public QUtiMimeConverter
+{
+public:
+ QString utiForMime(const QString &mime) const override;
+ QString mimeForUti(const QString &uti) const override;
+ QVariant convertToMime(const QString &mime, const QList<QByteArray> &data,
+ const QString &uti) const override;
+ QList<QByteArray> convertFromMime(const QString &mime, const QVariant &data,
+ const QString &uti) const override;
+};
+
+QString QMacMimePlainTextFallback::utiForMime(const QString &mime) const
+{
+ if (mime == "text/plain"_L1)
+ return "public.text"_L1;
+ return QString();
+}
+
+QString QMacMimePlainTextFallback::mimeForUti(const QString &uti) const
+{
+ if (uti == "public.text"_L1)
+ return "text/plain"_L1;
+ return QString();
+}
+
+QVariant
+QMacMimePlainTextFallback::convertToMime(const QString &mimetype,
+ const QList<QByteArray> &data, const QString &uti) const
+{
+ if (data.count() > 1)
+ qWarning("QMacMimePlainTextFallback: Cannot handle multiple member data");
+
+ if (uti == "public.text"_L1) {
+ // Note that public.text is documented by Apple to have an undefined encoding. From
+ // testing it seems that utf8 is normally used, at least by Safari on iOS.
+ const QByteArray &firstData = data.first();
+ return QString(QCFString(CFStringCreateWithBytes(kCFAllocatorDefault,
+ reinterpret_cast<const UInt8 *>(firstData.constData()),
+ firstData.size(), kCFStringEncodingUTF8, false)));
+ } else {
+ qWarning("QMime::convertToMime: unhandled mimetype: %s", qPrintable(mimetype));
+ }
+ return QVariant();
+}
+
+QList<QByteArray>
+QMacMimePlainTextFallback::convertFromMime(const QString &, const QVariant &data,
+ const QString &uti) const
+{
+ QList<QByteArray> ret;
+ QString string = data.toString();
+ if (uti == "public.text"_L1)
+ ret.append(string.toUtf8());
+ return ret;
+}
+
+class QMacMimeUnicodeText : public QUtiMimeConverter
+{
+public:
+ QString utiForMime(const QString &mime) const override;
+ QString mimeForUti(const QString &uti) const override;
+ QVariant convertToMime(const QString &mime, const QList<QByteArray> &data,
+ const QString &uti) const override;
+ QList<QByteArray> convertFromMime(const QString &mime, const QVariant &data,
+ const QString &uti) const override;
+};
+
+QString QMacMimeUnicodeText::utiForMime(const QString &mime) const
+{
+ if (mime == "text/plain"_L1)
+ return "public.utf16-plain-text"_L1;
+ if (qsizetype i = mime.indexOf("charset="_L1); i >= 0) {
+ QString cs(mime.mid(i + 8).toLower());
+ i = cs.indexOf(u';');
+ if (i >= 0)
+ cs = cs.left(i);
+ if (cs == "system"_L1)
+ return "public.utf8-plain-text"_L1;
+ else if (cs == "iso-10646-ucs-2"_L1 || cs == "utf16"_L1)
+ return "public.utf16-plain-text"_L1;
+ }
+ return QString();
+}
+
+QString QMacMimeUnicodeText::mimeForUti(const QString &uti) const
+{
+ if (uti == "public.utf16-plain-text"_L1 || uti == "public.utf8-plain-text"_L1)
+ return "text/plain"_L1;
+ return QString();
+}
+
+QVariant
+QMacMimeUnicodeText::convertToMime(const QString &mimetype,
+ const QList<QByteArray> &data, const QString &uti) const
+{
+ if (data.count() > 1)
+ qWarning("QMacMimeUnicodeText: Cannot handle multiple member data");
+ const QByteArray &firstData = data.first();
+ // I can only handle two types (system and unicode) so deal with them that way
+ QVariant ret;
+ if (uti == "public.utf8-plain-text"_L1) {
+ ret = QString::fromUtf8(firstData);
+ } else if (uti == "public.utf16-plain-text"_L1) {
+ QString str = QStringDecoder(QStringDecoder::Utf16)(firstData);
+ ret = str;
+ } else {
+ qWarning("QMime::convertToMime: unhandled mimetype: %s", qPrintable(mimetype));
+ }
+ return ret;
+}
+
+QList<QByteArray>
+QMacMimeUnicodeText::convertFromMime(const QString &, const QVariant &data,
+ const QString &uti) const
+{
+ QList<QByteArray> ret;
+ QString string = data.toString();
+ if (uti == "public.utf8-plain-text"_L1)
+ ret.append(string.toUtf8());
+ else if (uti == "public.utf16-plain-text"_L1) {
+ QStringEncoder::Flags f;
+#if defined(Q_OS_MACOS)
+ // Some applications such as Microsoft Excel, don't deal well with
+ // a BOM present, so we follow the traditional approach of Qt on
+ // macOS to not generate public.utf16-plain-text with a BOM.
+ f = QStringEncoder::Flag::Default;
+#else
+ // Whereas iOS applications will fail to paste if we do _not_
+ // include a BOM in the public.utf16-plain-text content, most
+ // likely due to converting the data using NSUTF16StringEncoding
+ // which assumes big-endian byte order if there is no BOM.
+ f = QStringEncoder::Flag::WriteBom;
+#endif
+ QStringEncoder encoder(QStringEncoder::Utf16, f);
+ ret.append(encoder(string));
+ }
+ return ret;
+}
+
+class QMacMimeHTMLText : public QUtiMimeConverter
+{
+public:
+ QString utiForMime(const QString &mime) const override;
+ QString mimeForUti(const QString &uti) const override;
+ QVariant convertToMime(const QString &mime, const QList<QByteArray> &data,
+ const QString &uti) const override;
+ QList<QByteArray> convertFromMime(const QString &mime, const QVariant &data,
+ const QString &uti) const override;
+};
+
+QString QMacMimeHTMLText::utiForMime(const QString &mime) const
+{
+ if (mime == "text/html"_L1)
+ return "public.html"_L1;
+ return QString();
+}
+
+QString QMacMimeHTMLText::mimeForUti(const QString &uti) const
+{
+ if (uti == "public.html"_L1)
+ return "text/html"_L1;
+ return QString();
+}
+
+QVariant
+QMacMimeHTMLText::convertToMime(const QString &mimeType,
+ const QList<QByteArray> &data, const QString &uti) const
+{
+ if (!canConvert(mimeType, uti))
+ return QVariant();
+ if (data.count() > 1)
+ qWarning("QMacMimeHTMLText: Cannot handle multiple member data");
+ return data.first();
+}
+
+QList<QByteArray>
+QMacMimeHTMLText::convertFromMime(const QString &mime,
+ const QVariant &data, const QString &uti) const
+{
+ QList<QByteArray> ret;
+ if (!canConvert(mime, uti))
+ return ret;
+ ret.append(data.toByteArray());
+ return ret;
+}
+
+class QMacMimeRtfText : public QUtiMimeConverter
+{
+public:
+ QString utiForMime(const QString &mime) const override;
+ QString mimeForUti(const QString &uti) const override;
+ QVariant convertToMime(const QString &mime, const QList<QByteArray> &data,
+ const QString &uti) const override;
+ QList<QByteArray> convertFromMime(const QString &mime, const QVariant &data,
+ const QString &uti) const override;
+};
+
+QString QMacMimeRtfText::utiForMime(const QString &mime) const
+{
+ if (mime == "text/html"_L1)
+ return "public.rtf"_L1;
+ return QString();
+}
+
+QString QMacMimeRtfText::mimeForUti(const QString &uti) const
+{
+ if (uti == "public.rtf"_L1)
+ return "text/html"_L1;
+ return QString();
+}
+
+QVariant
+QMacMimeRtfText::convertToMime(const QString &mimeType,
+ const QList<QByteArray> &data, const QString &uti) const
+{
+ if (!canConvert(mimeType, uti))
+ return QVariant();
+ if (data.count() > 1)
+ qWarning("QMacMimeHTMLText: Cannot handle multiple member data");
+
+ // Read RTF into to NSAttributedString, then convert the string to HTML
+ NSAttributedString *string = [[NSAttributedString alloc] initWithData:data.at(0).toNSData()
+ options:@{NSDocumentTypeDocumentAttribute: NSRTFTextDocumentType}
+ documentAttributes:nil
+ error:nil];
+
+ NSError *error;
+ NSRange range = NSMakeRange(0, [string length]);
+ NSDictionary *dict = @{NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType};
+ NSData *htmlData = [string dataFromRange:range documentAttributes:dict error:&error];
+ return QByteArray::fromNSData(htmlData);
+}
+
+QList<QByteArray>
+QMacMimeRtfText::convertFromMime(const QString &mime,
+ const QVariant &data, const QString &uti) const
+{
+ QList<QByteArray> ret;
+ if (!canConvert(mime, uti))
+ return ret;
+
+ NSAttributedString *string = [[NSAttributedString alloc] initWithData:data.toByteArray().toNSData()
+ options:@{NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType}
+ documentAttributes:nil
+ error:nil];
+
+ NSError *error;
+ NSRange range = NSMakeRange(0, [string length]);
+ NSDictionary *dict = @{NSDocumentTypeDocumentAttribute: NSRTFTextDocumentType};
+ NSData *rtfData = [string dataFromRange:range documentAttributes:dict error:&error];
+ ret << QByteArray::fromNSData(rtfData);
+ return ret;
+}
+
+class QMacMimeFileUri : public QUtiMimeConverter
+{
+public:
+ QString utiForMime(const QString &mime) const override;
+ QString mimeForUti(const QString &uti) const override;
+ QVariant convertToMime(const QString &mime, const QList<QByteArray> &data,
+ const QString &uti) const override;
+ QList<QByteArray> convertFromMime(const QString &mime, const QVariant &data,
+ const QString &uti) const override;
+ int count(const QMimeData *mimeData) const override;
+};
+
+QString QMacMimeFileUri::utiForMime(const QString &mime) const
+{
+ if (mime == "text/uri-list"_L1)
+ return "public.file-url"_L1;
+ return QString();
+}
+
+QString QMacMimeFileUri::mimeForUti(const QString &uti) const
+{
+ if (uti == "public.file-url"_L1)
+ return "text/uri-list"_L1;
+ return QString();
+}
+
+QVariant
+QMacMimeFileUri::convertToMime(const QString &mime,
+ const QList<QByteArray> &data, const QString &uti) const
+{
+ if (!canConvert(mime, uti))
+ return QVariant();
+ QList<QVariant> ret;
+ for (int i = 0; i < data.size(); ++i) {
+ const QByteArray &a = data.at(i);
+ NSString *urlString = [[[NSString alloc] initWithBytesNoCopy:(void *)a.data() length:a.size()
+ encoding:NSUTF8StringEncoding freeWhenDone:NO] autorelease];
+ NSURL *nsurl = [NSURL URLWithString:urlString];
+ QUrl url;
+ // OS X 10.10 sends file references instead of file paths
+ if ([nsurl isFileReferenceURL]) {
+ url = QUrl::fromNSURL([nsurl filePathURL]);
+ } else {
+ url = QUrl::fromNSURL(nsurl);
+ }
+
+ if (url.host().toLower() == "localhost"_L1)
+ url.setHost(QString());
+
+ url.setPath(url.path().normalized(QString::NormalizationForm_C));
+ ret.append(url);
+ }
+ return QVariant(ret);
+}
+
+QList<QByteArray>
+QMacMimeFileUri::convertFromMime(const QString &mime,
+ const QVariant &data, const QString &uti) const
+{
+ QList<QByteArray> ret;
+ if (!canConvert(mime, uti))
+ return ret;
+ QList<QVariant> urls = data.toList();
+ for (int i = 0; i < urls.size(); ++i) {
+ QUrl url = urls.at(i).toUrl();
+ if (url.scheme().isEmpty())
+ url.setScheme("file"_L1);
+ if (url.scheme() == "file"_L1) {
+ if (url.host().isEmpty())
+ url.setHost("localhost"_L1);
+ url.setPath(url.path().normalized(QString::NormalizationForm_D));
+ }
+ if (url.isLocalFile())
+ ret.append(url.toEncoded());
+ }
+ return ret;
+}
+
+int QMacMimeFileUri::count(const QMimeData *mimeData) const
+{
+ return mimeData->urls().count();
+}
+
+class QMacMimeUrl : public QUtiMimeConverter
+{
+public:
+ QString utiForMime(const QString &mime) const override;
+ QString mimeForUti(const QString &uti) const override;
+ QVariant convertToMime(const QString &mime, const QList<QByteArray> &data,
+ const QString &uti) const override;
+ QList<QByteArray> convertFromMime(const QString &mime, const QVariant &data,
+ const QString &uti) const override;
+};
+
+QString QMacMimeUrl::utiForMime(const QString &mime) const
+{
+ if (mime.startsWith("text/uri-list"_L1))
+ return "public.url"_L1;
+ return QString();
+}
+
+QString QMacMimeUrl::mimeForUti(const QString &uti) const
+{
+ if (uti == "public.url"_L1)
+ return "text/uri-list"_L1;
+ return QString();
+}
+
+QVariant QMacMimeUrl::convertToMime(const QString &mime,
+ const QList<QByteArray> &data, const QString &uti) const
+{
+ if (!canConvert(mime, uti))
+ return QVariant();
+
+ QList<QVariant> ret;
+ for (int i=0; i<data.size(); ++i) {
+ QUrl url = QUrl::fromEncoded(data.at(i));
+ if (url.host().toLower() == "localhost"_L1)
+ url.setHost(QString());
+ url.setPath(url.path().normalized(QString::NormalizationForm_C));
+ ret.append(url);
+ }
+ return QVariant(ret);
+}
+
+QList<QByteArray> QMacMimeUrl::convertFromMime(const QString &mime,
+ const QVariant &data, const QString &uti) const
+{
+ QList<QByteArray> ret;
+ if (!canConvert(mime, uti))
+ return ret;
+
+ QList<QVariant> urls = data.toList();
+ for (int i=0; i<urls.size(); ++i) {
+ QUrl url = urls.at(i).toUrl();
+ if (url.scheme().isEmpty())
+ url.setScheme("file"_L1);
+ if (url.scheme() == "file"_L1) {
+ if (url.host().isEmpty())
+ url.setHost("localhost"_L1);
+ url.setPath(url.path().normalized(QString::NormalizationForm_D));
+ }
+ ret.append(url.toEncoded());
+ }
+ return ret;
+}
+
+class QMacMimeVCard : public QUtiMimeConverter
+{
+public:
+ QString utiForMime(const QString &mime) const override;
+ QString mimeForUti(const QString &uti) const override;
+ QVariant convertToMime(const QString &mime, const QList<QByteArray> &data,
+ const QString &uti) const override;
+ QList<QByteArray> convertFromMime(const QString &mime, const QVariant &data,
+ const QString &uti) const override;
+};
+
+QString QMacMimeVCard::utiForMime(const QString &mime) const
+{
+ if (mime.startsWith("text/vcard"_L1))
+ return "public.vcard"_L1;
+ return QString();
+}
+
+QString QMacMimeVCard::mimeForUti(const QString &uti) const
+{
+ if (uti == "public.vcard"_L1)
+ return "text/vcard"_L1;
+ return QString();
+}
+
+QVariant QMacMimeVCard::convertToMime(const QString &mime,
+ const QList<QByteArray> &data, const QString &uti) const
+{
+ if (!canConvert(mime, uti))
+ return QVariant();
+ QByteArray cards;
+ if (uti == "public.vcard"_L1) {
+ for (int i=0; i<data.size(); ++i)
+ cards += data[i];
+ }
+ return QVariant(cards);
+}
+
+QList<QByteArray> QMacMimeVCard::convertFromMime(const QString &mime,
+ const QVariant &data, const QString &uti) const
+{
+ QList<QByteArray> ret;
+ if (!canConvert(mime, uti))
+ return ret;
+
+ if (mime == "text/vcard"_L1)
+ ret.append(data.toString().toUtf8());
+ return ret;
+}
+
+extern QImage qt_mac_toQImage(CGImageRef image);
+extern CGImageRef qt_mac_toCGImage(const QImage &qImage);
+
+class QMacMimeTiff : public QUtiMimeConverter
+{
+public:
+ QString utiForMime(const QString &mime) const override;
+ QString mimeForUti(const QString &uti) const override;
+ QVariant convertToMime(const QString &mime, const QList<QByteArray> &data,
+ const QString &uti) const override;
+ QList<QByteArray> convertFromMime(const QString &mime, const QVariant &data,
+ const QString &uti) const override;
+};
+
+QString QMacMimeTiff::utiForMime(const QString &mime) const
+{
+ if (mime.startsWith("application/x-qt-image"_L1))
+ return "public.tiff"_L1;
+ return QString();
+}
+
+QString QMacMimeTiff::mimeForUti(const QString &uti) const
+{
+ if (uti == "public.tiff"_L1)
+ return "application/x-qt-image"_L1;
+ return QString();
+}
+
+QVariant QMacMimeTiff::convertToMime(const QString &mime,
+ const QList<QByteArray> &data, const QString &uti) const
+{
+ if (data.count() > 1)
+ qWarning("QMacMimeTiff: Cannot handle multiple member data");
+
+ if (!canConvert(mime, uti))
+ return QVariant();
+
+ QCFType<CFDataRef> tiffData = data.first().toRawCFData();
+ QCFType<CGImageSourceRef> imageSource = CGImageSourceCreateWithData(tiffData, 0);
+
+ if (QCFType<CGImageRef> image = CGImageSourceCreateImageAtIndex(imageSource, 0, 0))
+ return QVariant(qt_mac_toQImage(image));
+
+ return QVariant();
+}
+
+QList<QByteArray> QMacMimeTiff::convertFromMime(const QString &mime,
+ const QVariant &variant, const QString &uti) const
+{
+ if (!canConvert(mime, uti))
+ return QList<QByteArray>();
+
+ QCFType<CFMutableDataRef> data = CFDataCreateMutable(0, 0);
+ QCFType<CGImageDestinationRef> imageDestination = CGImageDestinationCreateWithData(data,
+ (CFStringRef)UTTypeTIFF.identifier, 1, 0);
+
+ if (!imageDestination)
+ return QList<QByteArray>();
+
+ QImage img = qvariant_cast<QImage>(variant);
+ NSDictionary *props = @{
+ static_cast<NSString *>(kCGImagePropertyPixelWidth): @(img.width()),
+ static_cast<NSString *>(kCGImagePropertyPixelHeight): @(img.height())
+ };
+
+ CGImageDestinationAddImage(imageDestination, qt_mac_toCGImage(img),
+ static_cast<CFDictionaryRef>(props));
+ CGImageDestinationFinalize(imageDestination);
+
+ return QList<QByteArray>() << QByteArray::fromCFData(data);
+}
+
+namespace QMacMimeRegistry {
+
+void registerBuiltInTypes()
+{
+ // Create QMacMimeAny first to put it at the end of globalMimeList
+ // with lowest priority. (the constructor prepends to the list)
+ new QMacMimeAny;
+
+ //standard types that we wrap
+ new QMacMimeTiff;
+ new QMacMimePlainTextFallback;
+ new QMacMimeUnicodeText;
+ new QMacMimeRtfText;
+ new QMacMimeHTMLText;
+ new QMacMimeFileUri;
+ new QMacMimeUrl;
+ new QMacMimeTypeName;
+ new QMacMimeVCard;
+}
+
+}
+
+QT_END_NAMESPACE
diff --git a/src/gui/platform/ios/PrivacyInfo.xcprivacy b/src/gui/platform/ios/PrivacyInfo.xcprivacy
new file mode 100644
index 0000000000..bde2b167c7
--- /dev/null
+++ b/src/gui/platform/ios/PrivacyInfo.xcprivacy
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>NSPrivacyTracking</key>
+ <false/>
+ <key>NSPrivacyCollectedDataTypes</key>
+ <array/>
+ <key>NSPrivacyTrackingDomains</key>
+ <array/>
+ <key>NSPrivacyAccessedAPITypes</key>
+ <array>
+ <dict>
+ <key>NSPrivacyAccessedAPIType</key>
+ <string>NSPrivacyAccessedAPICategorySystemBootTime</string>
+ <key>NSPrivacyAccessedAPITypeReasons</key>
+ <array>
+ <string>35F9.1</string> <!-- QUIView event handling -->
+ </array>
+ </dict>
+ </array>
+</dict>
+</plist>
diff --git a/src/gui/platform/macos/qcocoanativeinterface.mm b/src/gui/platform/macos/qcocoanativeinterface.mm
index a41f9b16da..cb6acb4496 100644
--- a/src/gui/platform/macos/qcocoanativeinterface.mm
+++ b/src/gui/platform/macos/qcocoanativeinterface.mm
@@ -1,7 +1,11 @@
// Copyright (C) 2020 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
-#include <QtGui/private/qopenglcontext_p.h>
+#include <QtGui/qtgui-config.h>
+#ifndef QT_NO_OPENGL
+# include <QtGui/private/qopenglcontext_p.h>
+#endif
+
#include <QtGui/private/qguiapplication_p.h>
#include <qpa/qplatformopenglcontext.h>
#include <qpa/qplatformintegration.h>
diff --git a/src/gui/platform/unix/dbusmenu/qdbusmenuconnection.cpp b/src/gui/platform/unix/dbusmenu/qdbusmenuconnection.cpp
index 9391b77f6a..1023b16662 100644
--- a/src/gui/platform/unix/dbusmenu/qdbusmenuconnection.cpp
+++ b/src/gui/platform/unix/dbusmenu/qdbusmenuconnection.cpp
@@ -40,14 +40,14 @@ QDBusMenuConnection::QDBusMenuConnection(QObject *parent, const QString &service
, m_connection(serviceName.isNull() ? QDBusConnection::sessionBus()
: QDBusConnection::connectToBus(QDBusConnection::SessionBus, serviceName))
, m_dbusWatcher(new QDBusServiceWatcher(StatusNotifierWatcherService, m_connection, QDBusServiceWatcher::WatchForRegistration, this))
- , m_statusNotifierHostRegistered(false)
+ , m_watcherRegistered(false)
{
#ifndef QT_NO_SYSTEMTRAYICON
- QDBusInterface systrayHost(StatusNotifierWatcherService, StatusNotifierWatcherPath, StatusNotifierWatcherService, m_connection);
- if (systrayHost.isValid() && systrayHost.property("IsStatusNotifierHostRegistered").toBool())
- m_statusNotifierHostRegistered = true;
+ // Start monitoring if any known tray-related services are registered.
+ if (m_connection.interface()->isServiceRegistered(StatusNotifierWatcherService))
+ m_watcherRegistered = true;
else
- qCDebug(qLcMenu) << "StatusNotifierHost is not registered";
+ qCDebug(qLcMenu) << "failed to find service" << StatusNotifierWatcherService;
#endif
}
diff --git a/src/gui/platform/unix/dbusmenu/qdbusmenuconnection_p.h b/src/gui/platform/unix/dbusmenu/qdbusmenuconnection_p.h
index 69713b12bd..37033e2fa3 100644
--- a/src/gui/platform/unix/dbusmenu/qdbusmenuconnection_p.h
+++ b/src/gui/platform/unix/dbusmenu/qdbusmenuconnection_p.h
@@ -39,7 +39,7 @@ public:
~QDBusMenuConnection();
QDBusConnection connection() const { return m_connection; }
QDBusServiceWatcher *dbusWatcher() const { return m_dbusWatcher; }
- bool isStatusNotifierHostRegistered() const { return m_statusNotifierHostRegistered; }
+ bool isWatcherRegistered() const { return m_watcherRegistered; }
#ifndef QT_NO_SYSTEMTRAYICON
bool registerTrayIconMenu(QDBusTrayIcon *item);
void unregisterTrayIconMenu(QDBusTrayIcon *item);
@@ -60,7 +60,7 @@ private:
QString m_serviceName;
QDBusConnection m_connection;
QDBusServiceWatcher *m_dbusWatcher;
- bool m_statusNotifierHostRegistered;
+ bool m_watcherRegistered;
};
QT_END_NAMESPACE
diff --git a/src/gui/platform/unix/dbusmenu/qdbusmenuregistrarproxy_p.h b/src/gui/platform/unix/dbusmenu/qdbusmenuregistrarproxy_p.h
index 4568fd2aa4..8041f3af0a 100644
--- a/src/gui/platform/unix/dbusmenu/qdbusmenuregistrarproxy_p.h
+++ b/src/gui/platform/unix/dbusmenu/qdbusmenuregistrarproxy_p.h
@@ -63,7 +63,7 @@ public Q_SLOTS: // METHODS
{
QDBusMessage reply = call(QDBus::Block, QStringLiteral("GetMenuForWindow"), windowId);
QList<QVariant> arguments = reply.arguments();
- if (reply.type() == QDBusMessage::ReplyMessage && arguments.count() == 2)
+ if (reply.type() == QDBusMessage::ReplyMessage && arguments.size() == 2)
menuObjectPath = qdbus_cast<QDBusObjectPath>(arguments.at(1));
return reply;
}
diff --git a/src/gui/platform/unix/dbusmenu/qdbusmenutypes.cpp b/src/gui/platform/unix/dbusmenu/qdbusmenutypes.cpp
index f70ce67d4e..b7fd035883 100644
--- a/src/gui/platform/unix/dbusmenu/qdbusmenutypes.cpp
+++ b/src/gui/platform/unix/dbusmenu/qdbusmenutypes.cpp
@@ -204,7 +204,7 @@ QString QDBusMenuItem::convertMnemonic(const QString &label)
// convert only the first occurrence of ampersand which is not at the end
// dbusmenu uses underscore instead of ampersand
int idx = label.indexOf(u'&');
- if (idx < 0 || idx == label.length() - 1)
+ if (idx < 0 || idx == label.size() - 1)
return label;
QString ret(label);
ret[idx] = u'_';
@@ -217,19 +217,19 @@ QDBusMenuShortcut QDBusMenuItem::convertKeySequence(const QKeySequence &sequence
QDBusMenuShortcut shortcut;
for (int i = 0; i < sequence.count(); ++i) {
QStringList tokens;
- int key = sequence[i].toCombined();
- if (key & Qt::MetaModifier)
+ auto modifiers = sequence[i].keyboardModifiers();
+ if (modifiers & Qt::MetaModifier)
tokens << QStringLiteral("Super");
- if (key & Qt::ControlModifier)
+ if (modifiers & Qt::ControlModifier)
tokens << QStringLiteral("Control");
- if (key & Qt::AltModifier)
+ if (modifiers & Qt::AltModifier)
tokens << QStringLiteral("Alt");
- if (key & Qt::ShiftModifier)
+ if (modifiers & Qt::ShiftModifier)
tokens << QStringLiteral("Shift");
- if (key & Qt::KeypadModifier)
+ if (modifiers & Qt::KeypadModifier)
tokens << QStringLiteral("Num");
- QString keyName = QKeySequencePrivate::keyName(key, QKeySequence::PortableText);
+ QString keyName = QKeySequencePrivate::keyName(sequence[i].key(), QKeySequence::PortableText);
if (keyName == "+"_L1)
tokens << QStringLiteral("plus");
else if (keyName == "-"_L1)
@@ -271,7 +271,7 @@ QDebug operator<<(QDebug d, const QDBusMenuLayoutItem &item)
{
QDebugStateSaver saver(d);
d.nospace();
- d << "QDBusMenuLayoutItem(id=" << item.m_id << ", properties=" << item.m_properties << ", " << item.m_children.count() << " children)";
+ d << "QDBusMenuLayoutItem(id=" << item.m_id << ", properties=" << item.m_properties << ", " << item.m_children.size() << " children)";
return d;
}
#endif
diff --git a/src/gui/platform/unix/dbustray/qdbustrayicon.cpp b/src/gui/platform/unix/dbustray/qdbustrayicon.cpp
index 18334e5715..0dff9b598e 100644
--- a/src/gui/platform/unix/dbustray/qdbustrayicon.cpp
+++ b/src/gui/platform/unix/dbustray/qdbustrayicon.cpp
@@ -198,7 +198,10 @@ QTemporaryFile *QDBusTrayIcon::tempIcon(const QIcon &icon)
if (!necessary)
return nullptr;
QTemporaryFile *ret = new QTemporaryFile(tempFileTemplate(), this);
- ret->open();
+ if (!ret->open()) {
+ delete ret;
+ return nullptr;
+ }
icon.pixmap(QSize(22, 22)).save(ret);
ret->close();
return ret;
@@ -331,8 +334,11 @@ void QDBusTrayIcon::notificationClosed(uint id, uint reason)
bool QDBusTrayIcon::isSystemTrayAvailable() const
{
QDBusMenuConnection * conn = const_cast<QDBusTrayIcon *>(this)->dBusConnection();
- qCDebug(qLcTray) << conn->isStatusNotifierHostRegistered();
- return conn->isStatusNotifierHostRegistered();
+
+ // If the KDE watcher service is registered, we must be on a desktop
+ // where a StatusNotifier-conforming system tray exists.
+ qCDebug(qLcTray) << conn->isWatcherRegistered();
+ return conn->isWatcherRegistered();
}
QT_END_NAMESPACE
diff --git a/src/gui/platform/unix/dbustray/qdbustraytypes.cpp b/src/gui/platform/unix/dbustray/qdbustraytypes.cpp
index b2d87d7b8c..accbd87e7e 100644
--- a/src/gui/platform/unix/dbustray/qdbustraytypes.cpp
+++ b/src/gui/platform/unix/dbustray/qdbustraytypes.cpp
@@ -45,7 +45,7 @@ QXdgDBusImageVector iconToQXdgDBusImageVector(const QIcon &icon)
bool hasSmallIcon = false;
bool hasMediumIcon = false;
QList<QSize> toRemove;
- for (const QSize &size : qAsConst(sizes)) {
+ for (const QSize &size : std::as_const(sizes)) {
int maxSize = qMax(size.width(), size.height());
if (maxSize <= IconNormalSmallSize)
hasSmallIcon = true;
@@ -54,7 +54,7 @@ QXdgDBusImageVector iconToQXdgDBusImageVector(const QIcon &icon)
else if (maxSize > IconSizeLimit)
toRemove << size;
}
- for (const QSize &size : qAsConst(toRemove))
+ for (const QSize &size : std::as_const(toRemove))
sizes.removeOne(size);
if (!hasSmallIcon)
sizes.append(QSize(IconNormalSmallSize, IconNormalSmallSize));
@@ -62,7 +62,7 @@ QXdgDBusImageVector iconToQXdgDBusImageVector(const QIcon &icon)
sizes.append(QSize(IconNormalMediumSize, IconNormalMediumSize));
ret.reserve(sizes.size());
- for (const QSize &size : qAsConst(sizes)) {
+ for (const QSize &size : std::as_const(sizes)) {
// Protocol specifies ARGB32 format in network byte order
QImage im = engine->pixmap(size, QIcon::Normal, QIcon::Off).toImage().convertToFormat(QImage::Format_ARGB32);
// letterbox if necessary to make it square
diff --git a/src/gui/platform/unix/dbustray/qstatusnotifieritemadaptor.cpp b/src/gui/platform/unix/dbustray/qstatusnotifieritemadaptor.cpp
index 27a14e408a..2f6c13b6cf 100644
--- a/src/gui/platform/unix/dbustray/qstatusnotifieritemadaptor.cpp
+++ b/src/gui/platform/unix/dbustray/qstatusnotifieritemadaptor.cpp
@@ -136,6 +136,12 @@ void QStatusNotifierItemAdaptor::ContextMenu(int x, int y)
emit m_trayIcon->activated(QPlatformSystemTrayIcon::Context);
}
+void QStatusNotifierItemAdaptor::ProvideXdgActivationToken(const QString &token)
+{
+ qCDebug(qLcTray) << token;
+ qputenv("XDG_ACTIVATION_TOKEN", token.toUtf8());
+}
+
void QStatusNotifierItemAdaptor::Scroll(int w, const QString &s)
{
qCDebug(qLcTray) << w << s;
diff --git a/src/gui/platform/unix/dbustray/qstatusnotifieritemadaptor_p.h b/src/gui/platform/unix/dbustray/qstatusnotifieritemadaptor_p.h
index 286aafa9bb..103fc974dd 100644
--- a/src/gui/platform/unix/dbustray/qstatusnotifieritemadaptor_p.h
+++ b/src/gui/platform/unix/dbustray/qstatusnotifieritemadaptor_p.h
@@ -73,6 +73,9 @@ class QStatusNotifierItemAdaptor: public QDBusAbstractAdaptor
" <property access=\"read\" type=\"(sa(iiay)ss)\" name=\"ToolTip\">\n"
" <annotation value=\"QXdgDBusToolTipStruct\" name=\"org.qtproject.QtDBus.QtTypeName\"/>\n"
" </property>\n"
+" <method name=\"ProvideXdgActivationToken\">\n"
+" <arg name=\"token\" type=\"s\" direction=\"in\"/>\n"
+" </method>\n"
" <method name=\"ContextMenu\">\n"
" <arg direction=\"in\" type=\"i\" name=\"x\"/>\n"
" <arg direction=\"in\" type=\"i\" name=\"y\"/>\n"
@@ -150,6 +153,7 @@ public: // PROPERTIES
public Q_SLOTS: // METHODS
void Activate(int x, int y);
void ContextMenu(int x, int y);
+ void ProvideXdgActivationToken(const QString &token);
void Scroll(int delta, const QString &orientation);
void SecondaryActivate(int x, int y);
Q_SIGNALS: // SIGNALS
diff --git a/src/gui/platform/unix/dbustray/qxdgnotificationproxy_p.h b/src/gui/platform/unix/dbustray/qxdgnotificationproxy_p.h
index fe61723bd2..dfbc64f33b 100644
--- a/src/gui/platform/unix/dbustray/qxdgnotificationproxy_p.h
+++ b/src/gui/platform/unix/dbustray/qxdgnotificationproxy_p.h
@@ -77,7 +77,7 @@ public Q_SLOTS: // METHODS
inline QDBusReply<QString> getServerInformation(QString &vendor, QString &version, QString &specVersion)
{
QDBusMessage reply = call(QDBus::Block, QStringLiteral("GetServerInformation"));
- if (reply.type() == QDBusMessage::ReplyMessage && reply.arguments().count() == 4) {
+ if (reply.type() == QDBusMessage::ReplyMessage && reply.arguments().size() == 4) {
vendor = qdbus_cast<QString>(reply.arguments().at(1));
version = qdbus_cast<QString>(reply.arguments().at(2));
specVersion = qdbus_cast<QString>(reply.arguments().at(3));
diff --git a/src/gui/platform/unix/qgenericunixservices.cpp b/src/gui/platform/unix/qgenericunixservices.cpp
index a0e5466c58..bfd2556b1e 100644
--- a/src/gui/platform/unix/qgenericunixservices.cpp
+++ b/src/gui/platform/unix/qgenericunixservices.cpp
@@ -3,6 +3,11 @@
#include "qgenericunixservices_p.h"
#include <QtGui/private/qtguiglobal_p.h>
+#include "qguiapplication.h"
+#include "qwindow.h"
+#include <QtGui/qpa/qplatformwindow_p.h>
+#include <QtGui/qpa/qplatformwindow.h>
+#include <QtGui/qpa/qplatformnativeinterface.h>
#include <QtCore/QDebug>
#include <QtCore/QFile>
@@ -126,8 +131,13 @@ static inline bool detectWebBrowser(const QByteArray &desktop,
return false;
}
-static inline bool launch(const QString &launcher, const QUrl &url)
+static inline bool launch(const QString &launcher, const QUrl &url,
+ const QString &xdgActivationToken)
{
+ if (!xdgActivationToken.isEmpty()) {
+ qputenv("XDG_ACTIVATION_TOKEN", xdgActivationToken.toUtf8());
+ }
+
const QString command = launcher + u' ' + QLatin1StringView(url.toEncoded());
if (debug)
qDebug("Launching %s", qPrintable(command));
@@ -143,16 +153,20 @@ static inline bool launch(const QString &launcher, const QUrl &url)
#endif
if (!ok)
qWarning("Launch failed (%s)", qPrintable(command));
+
+ qunsetenv("XDG_ACTIVATION_TOKEN");
+
return ok;
}
#if QT_CONFIG(dbus)
static inline bool checkNeedPortalSupport()
{
- return !QStandardPaths::locate(QStandardPaths::RuntimeLocation, "flatpak-info"_L1).isEmpty() || qEnvironmentVariableIsSet("SNAP");
+ return QFileInfo::exists("/.flatpak-info"_L1) || qEnvironmentVariableIsSet("SNAP");
}
-static inline QDBusMessage xdgDesktopPortalOpenFile(const QUrl &url)
+static inline QDBusMessage xdgDesktopPortalOpenFile(const QUrl &url, const QString &parentWindow,
+ const QString &xdgActivationToken)
{
// DBus signature:
// OpenFile (IN s parent_window,
@@ -163,8 +177,7 @@ static inline QDBusMessage xdgDesktopPortalOpenFile(const QUrl &url)
// handle_token (s) - A string that will be used as the last element of the @handle.
// writable (b) - Whether to allow the chosen application to write to the file.
-#ifdef O_PATH
- const int fd = qt_safe_open(QFile::encodeName(url.toLocalFile()), O_PATH);
+ const int fd = qt_safe_open(QFile::encodeName(url.toLocalFile()), O_RDONLY);
if (fd != -1) {
QDBusMessage message = QDBusMessage::createMethodCall("org.freedesktop.portal.Desktop"_L1,
"/org/freedesktop/portal/desktop"_L1,
@@ -174,21 +187,22 @@ static inline QDBusMessage xdgDesktopPortalOpenFile(const QUrl &url)
QDBusUnixFileDescriptor descriptor;
descriptor.giveFileDescriptor(fd);
- const QVariantMap options = {{"writable"_L1, true}};
+ QVariantMap options = {};
+
+ if (!xdgActivationToken.isEmpty()) {
+ options.insert("activation_token"_L1, xdgActivationToken);
+ }
- // FIXME parent_window_id
- message << QString() << QVariant::fromValue(descriptor) << options;
+ message << parentWindow << QVariant::fromValue(descriptor) << options;
return QDBusConnection::sessionBus().call(message);
}
-#else
- Q_UNUSED(url);
-#endif
return QDBusMessage::createError(QDBusError::InternalError, qt_error_string());
}
-static inline QDBusMessage xdgDesktopPortalOpenUrl(const QUrl &url)
+static inline QDBusMessage xdgDesktopPortalOpenUrl(const QUrl &url, const QString &parentWindow,
+ const QString &xdgActivationToken)
{
// DBus signature:
// OpenURI (IN s parent_window,
@@ -206,12 +220,19 @@ static inline QDBusMessage xdgDesktopPortalOpenUrl(const QUrl &url)
"org.freedesktop.portal.OpenURI"_L1,
"OpenURI"_L1);
// FIXME parent_window_id and handle writable option
- message << QString() << url.toString() << QVariantMap();
+ QVariantMap options;
+
+ if (!xdgActivationToken.isEmpty()) {
+ options.insert("activation_token"_L1, xdgActivationToken);
+ }
+
+ message << parentWindow << url.toString() << options;
return QDBusConnection::sessionBus().call(message);
}
-static inline QDBusMessage xdgDesktopPortalSendEmail(const QUrl &url)
+static inline QDBusMessage xdgDesktopPortalSendEmail(const QUrl &url, const QString &parentWindow,
+ const QString &xdgActivationToken)
{
// DBus signature:
// ComposeEmail (IN s parent_window,
@@ -246,72 +267,274 @@ static inline QDBusMessage xdgDesktopPortalSendEmail(const QUrl &url)
options.insert("attachment_fds"_L1, QVariant::fromValue(attachments));
#endif
+ if (!xdgActivationToken.isEmpty()) {
+ options.insert("activation_token"_L1, xdgActivationToken);
+ }
+
QDBusMessage message = QDBusMessage::createMethodCall("org.freedesktop.portal.Desktop"_L1,
"/org/freedesktop/portal/desktop"_L1,
"org.freedesktop.portal.Email"_L1,
"ComposeEmail"_L1);
- // FIXME parent_window_id
- message << QString() << options;
+ message << parentWindow << options;
return QDBusConnection::sessionBus().call(message);
}
+
+namespace {
+struct XDGDesktopColor
+{
+ double r = 0;
+ double g = 0;
+ double b = 0;
+
+ QColor toQColor() const
+ {
+ constexpr auto rgbMax = 255;
+ return { static_cast<int>(r * rgbMax), static_cast<int>(g * rgbMax),
+ static_cast<int>(b * rgbMax) };
+ }
+};
+
+const QDBusArgument &operator>>(const QDBusArgument &argument, XDGDesktopColor &myStruct)
+{
+ argument.beginStructure();
+ argument >> myStruct.r >> myStruct.g >> myStruct.b;
+ argument.endStructure();
+ return argument;
+}
+
+class XdgDesktopPortalColorPicker : public QPlatformServiceColorPicker
+{
+ Q_OBJECT
+public:
+ XdgDesktopPortalColorPicker(const QString &parentWindowId, QWindow *parent)
+ : QPlatformServiceColorPicker(parent), m_parentWindowId(parentWindowId)
+ {
+ }
+
+ void pickColor() override
+ {
+ // DBus signature:
+ // PickColor (IN s parent_window,
+ // IN a{sv} options
+ // OUT o handle)
+ // Options:
+ // handle_token (s) - A string that will be used as the last element of the @handle.
+
+ QDBusMessage message = QDBusMessage::createMethodCall(
+ "org.freedesktop.portal.Desktop"_L1, "/org/freedesktop/portal/desktop"_L1,
+ "org.freedesktop.portal.Screenshot"_L1, "PickColor"_L1);
+ message << m_parentWindowId << QVariantMap();
+
+ QDBusPendingCall pendingCall = QDBusConnection::sessionBus().asyncCall(message);
+ auto watcher = new QDBusPendingCallWatcher(pendingCall, this);
+ connect(watcher, &QDBusPendingCallWatcher::finished, this,
+ [this](QDBusPendingCallWatcher *watcher) {
+ watcher->deleteLater();
+ QDBusPendingReply<QDBusObjectPath> reply = *watcher;
+ if (reply.isError()) {
+ qWarning("DBus call to pick color failed: %s",
+ qPrintable(reply.error().message()));
+ Q_EMIT colorPicked({});
+ } else {
+ QDBusConnection::sessionBus().connect(
+ "org.freedesktop.portal.Desktop"_L1, reply.value().path(),
+ "org.freedesktop.portal.Request"_L1, "Response"_L1, this,
+ // clang-format off
+ SLOT(gotColorResponse(uint,QVariantMap))
+ // clang-format on
+ );
+ }
+ });
+ }
+
+private Q_SLOTS:
+ void gotColorResponse(uint result, const QVariantMap &map)
+ {
+ if (result != 0)
+ return;
+ if (map.contains(u"color"_s)) {
+ XDGDesktopColor color{};
+ map.value(u"color"_s).value<QDBusArgument>() >> color;
+ Q_EMIT colorPicked(color.toQColor());
+ } else {
+ Q_EMIT colorPicked({});
+ }
+ deleteLater();
+ }
+
+private:
+ const QString m_parentWindowId;
+};
+} // namespace
+
#endif // QT_CONFIG(dbus)
+QGenericUnixServices::QGenericUnixServices()
+{
+#if QT_CONFIG(dbus)
+ if (qEnvironmentVariableIntValue("QT_NO_XDG_DESKTOP_PORTAL") > 0) {
+ return;
+ }
+ QDBusMessage message = QDBusMessage::createMethodCall(
+ "org.freedesktop.portal.Desktop"_L1, "/org/freedesktop/portal/desktop"_L1,
+ "org.freedesktop.DBus.Properties"_L1, "Get"_L1);
+ message << "org.freedesktop.portal.Screenshot"_L1
+ << "version"_L1;
+
+ QDBusPendingCall pendingCall = QDBusConnection::sessionBus().asyncCall(message);
+ auto watcher = new QDBusPendingCallWatcher(pendingCall);
+ QObject::connect(watcher, &QDBusPendingCallWatcher::finished, watcher,
+ [this](QDBusPendingCallWatcher *watcher) {
+ watcher->deleteLater();
+ QDBusPendingReply<QVariant> reply = *watcher;
+ if (!reply.isError() && reply.value().toUInt() >= 2)
+ m_hasScreenshotPortalWithColorPicking = true;
+ });
+
+#endif
+}
+
+QPlatformServiceColorPicker *QGenericUnixServices::colorPicker(QWindow *parent)
+{
+#if QT_CONFIG(dbus)
+ // Make double sure that we are in a wayland environment. In particular check
+ // WAYLAND_DISPLAY so also XWayland apps benefit from portal-based color picking.
+ // Outside wayland we'll rather rely on other means than the XDG desktop portal.
+ if (!qEnvironmentVariableIsEmpty("WAYLAND_DISPLAY")
+ || QGuiApplication::platformName().startsWith("wayland"_L1)) {
+ return new XdgDesktopPortalColorPicker(portalWindowIdentifier(parent), parent);
+ }
+ return nullptr;
+#else
+ Q_UNUSED(parent);
+ return nullptr;
+#endif
+}
+
QByteArray QGenericUnixServices::desktopEnvironment() const
{
static const QByteArray result = detectDesktopEnvironment();
return result;
}
+template<typename F>
+void runWithXdgActivationToken(F &&functionToCall)
+{
+#if QT_CONFIG(wayland)
+ QWindow *window = qGuiApp->focusWindow();
+
+ if (!window) {
+ functionToCall({});
+ return;
+ }
+
+ auto waylandApp = dynamic_cast<QNativeInterface::QWaylandApplication *>(
+ qGuiApp->platformNativeInterface());
+ auto waylandWindow =
+ dynamic_cast<QNativeInterface::Private::QWaylandWindow *>(window->handle());
+
+ if (!waylandWindow || !waylandApp) {
+ functionToCall({});
+ return;
+ }
+
+ QObject::connect(waylandWindow,
+ &QNativeInterface::Private::QWaylandWindow::xdgActivationTokenCreated,
+ waylandWindow, functionToCall, Qt::SingleShotConnection);
+ waylandWindow->requestXdgActivationToken(waylandApp->lastInputSerial());
+#else
+ functionToCall({});
+#endif
+}
+
bool QGenericUnixServices::openUrl(const QUrl &url)
{
- if (url.scheme() == "mailto"_L1) {
-#if QT_CONFIG(dbus)
+ auto openUrlInternal = [this](const QUrl &url, const QString &xdgActivationToken) {
+ if (url.scheme() == "mailto"_L1) {
+# if QT_CONFIG(dbus)
+ if (checkNeedPortalSupport()) {
+ const QString parentWindow = QGuiApplication::focusWindow()
+ ? portalWindowIdentifier(QGuiApplication::focusWindow())
+ : QString();
+ QDBusError error = xdgDesktopPortalSendEmail(url, parentWindow, xdgActivationToken);
+ if (!error.isValid())
+ return true;
+
+ // service not running, fall back
+ }
+# endif
+ return openDocument(url);
+ }
+
+# if QT_CONFIG(dbus)
if (checkNeedPortalSupport()) {
- QDBusError error = xdgDesktopPortalSendEmail(url);
+ const QString parentWindow = QGuiApplication::focusWindow()
+ ? portalWindowIdentifier(QGuiApplication::focusWindow())
+ : QString();
+ QDBusError error = xdgDesktopPortalOpenUrl(url, parentWindow, xdgActivationToken);
if (!error.isValid())
return true;
+ }
+# endif
- // service not running, fall back
+ if (m_webBrowser.isEmpty()
+ && !detectWebBrowser(desktopEnvironment(), true, &m_webBrowser)) {
+ qWarning("Unable to detect a web browser to launch '%s'", qPrintable(url.toString()));
+ return false;
}
-#endif
- return openDocument(url);
- }
+ return launch(m_webBrowser, url, xdgActivationToken);
+ };
-#if QT_CONFIG(dbus)
- if (checkNeedPortalSupport()) {
- QDBusError error = xdgDesktopPortalOpenUrl(url);
- if (!error.isValid())
- return true;
- }
-#endif
+ if (QGuiApplication::platformName().startsWith("wayland"_L1)) {
+ runWithXdgActivationToken(
+ [openUrlInternal, url](const QString &token) { openUrlInternal(url, token); });
+
+ return true;
- if (m_webBrowser.isEmpty() && !detectWebBrowser(desktopEnvironment(), true, &m_webBrowser)) {
- qWarning("Unable to detect a web browser to launch '%s'", qPrintable(url.toString()));
- return false;
+ } else {
+ return openUrlInternal(url, QString());
}
- return launch(m_webBrowser, url);
}
bool QGenericUnixServices::openDocument(const QUrl &url)
{
-#if QT_CONFIG(dbus)
- if (checkNeedPortalSupport()) {
- QDBusError error = xdgDesktopPortalOpenFile(url);
- if (!error.isValid())
- return true;
- }
-#endif
+ auto openDocumentInternal = [this](const QUrl &url, const QString &xdgActivationToken) {
- if (m_documentLauncher.isEmpty() && !detectWebBrowser(desktopEnvironment(), false, &m_documentLauncher)) {
- qWarning("Unable to detect a launcher for '%s'", qPrintable(url.toString()));
- return false;
+# if QT_CONFIG(dbus)
+ if (checkNeedPortalSupport()) {
+ const QString parentWindow = QGuiApplication::focusWindow()
+ ? portalWindowIdentifier(QGuiApplication::focusWindow())
+ : QString();
+ QDBusError error = xdgDesktopPortalOpenFile(url, parentWindow, xdgActivationToken);
+ if (!error.isValid())
+ return true;
+ }
+# endif
+
+ if (m_documentLauncher.isEmpty()
+ && !detectWebBrowser(desktopEnvironment(), false, &m_documentLauncher)) {
+ qWarning("Unable to detect a launcher for '%s'", qPrintable(url.toString()));
+ return false;
+ }
+ return launch(m_documentLauncher, url, xdgActivationToken);
+ };
+
+ if (QGuiApplication::platformName().startsWith("wayland"_L1)) {
+ runWithXdgActivationToken([openDocumentInternal, url](const QString &token) {
+ openDocumentInternal(url, token);
+ });
+
+ return true;
+ } else {
+ return openDocumentInternal(url, QString());
}
- return launch(m_documentLauncher, url);
}
#else
+QGenericUnixServices::QGenericUnixServices() = default;
+
QByteArray QGenericUnixServices::desktopEnvironment() const
{
return QByteArrayLiteral("UNKNOWN");
@@ -331,6 +554,60 @@ bool QGenericUnixServices::openDocument(const QUrl &url)
return false;
}
+QPlatformServiceColorPicker *QGenericUnixServices::colorPicker(QWindow *parent)
+{
+ Q_UNUSED(parent);
+ return nullptr;
+}
+
#endif // QT_NO_MULTIPROCESS
+QString QGenericUnixServices::portalWindowIdentifier(QWindow *window)
+{
+ Q_UNUSED(window);
+ return QString();
+}
+
+bool QGenericUnixServices::hasCapability(Capability capability) const
+{
+ switch (capability) {
+ case Capability::ColorPicking:
+ return m_hasScreenshotPortalWithColorPicking;
+ }
+ return false;
+}
+
+void QGenericUnixServices::setApplicationBadge(qint64 number)
+{
+#if QT_CONFIG(dbus)
+ if (qGuiApp->desktopFileName().isEmpty()) {
+ qWarning("QGuiApplication::desktopFileName() is empty");
+ return;
+ }
+
+
+ const QString launcherUrl = QStringLiteral("application://") + qGuiApp->desktopFileName() + QStringLiteral(".desktop");
+ const qint64 count = qBound(0, number, 9999);
+ QVariantMap dbusUnityProperties;
+
+ if (count > 0) {
+ dbusUnityProperties[QStringLiteral("count")] = count;
+ dbusUnityProperties[QStringLiteral("count-visible")] = true;
+ } else {
+ dbusUnityProperties[QStringLiteral("count-visible")] = false;
+ }
+
+ auto signal = QDBusMessage::createSignal(QStringLiteral("/com/canonical/unity/launcherentry/")
+ + qGuiApp->applicationName(), QStringLiteral("com.canonical.Unity.LauncherEntry"), QStringLiteral("Update"));
+
+ signal.setArguments({launcherUrl, dbusUnityProperties});
+
+ QDBusConnection::sessionBus().send(signal);
+#else
+ Q_UNUSED(number)
+#endif
+}
+
QT_END_NAMESPACE
+
+#include "qgenericunixservices.moc"
diff --git a/src/gui/platform/unix/qgenericunixservices_p.h b/src/gui/platform/unix/qgenericunixservices_p.h
index 8d1228375f..56e15103f7 100644
--- a/src/gui/platform/unix/qgenericunixservices_p.h
+++ b/src/gui/platform/unix/qgenericunixservices_p.h
@@ -21,19 +21,27 @@
QT_BEGIN_NAMESPACE
+class QWindow;
+
class Q_GUI_EXPORT QGenericUnixServices : public QPlatformServices
{
public:
- QGenericUnixServices() {}
+ QGenericUnixServices();
QByteArray desktopEnvironment() const override;
+ bool hasCapability(Capability capability) const override;
bool openUrl(const QUrl &url) override;
bool openDocument(const QUrl &url) override;
+ QPlatformServiceColorPicker *colorPicker(QWindow *parent = nullptr) override;
+
+ void setApplicationBadge(qint64 number);
+ virtual QString portalWindowIdentifier(QWindow *window);
private:
QString m_webBrowser;
QString m_documentLauncher;
+ bool m_hasScreenshotPortalWithColorPicking = false;
};
QT_END_NAMESPACE
diff --git a/src/gui/platform/unix/qgenericunixthemes.cpp b/src/gui/platform/unix/qgenericunixthemes.cpp
index d90d5de098..fc4b2296d2 100644
--- a/src/gui/platform/unix/qgenericunixthemes.cpp
+++ b/src/gui/platform/unix/qgenericunixthemes.cpp
@@ -1,4 +1,4 @@
-// Copyright (C) 2016 The Qt Company Ltd.
+// Copyright (C) 2022 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#include "qgenericunixthemes_p.h"
@@ -33,6 +33,12 @@
#include <QDBusConnectionInterface>
#include <private/qdbusplatformmenu_p.h>
#include <private/qdbusmenubar_p.h>
+#include <private/qflatmap_p.h>
+#include <QJsonDocument>
+#include <QJsonArray>
+#include <QJsonObject>
+#include <QJsonValue>
+#include <QJsonParseError>
#endif
#if !defined(QT_NO_DBUS) && !defined(QT_NO_SYSTEMTRAYICON)
#include <private/qdbustrayicon_p.h>
@@ -41,6 +47,9 @@
#include <algorithm>
QT_BEGIN_NAMESPACE
+#ifndef QT_NO_DBUS
+Q_LOGGING_CATEGORY(lcQpaThemeDBus, "qt.qpa.theme.dbus")
+#endif
using namespace Qt::StringLiterals;
@@ -74,7 +83,7 @@ static bool isDBusTrayAvailable() {
static bool dbusTrayAvailableKnown = false;
if (!dbusTrayAvailableKnown) {
QDBusMenuConnection conn;
- if (conn.isStatusNotifierHostRegistered())
+ if (conn.isWatcherRegistered())
dbusTrayAvailable = true;
dbusTrayAvailableKnown = true;
qCDebug(qLcTray) << "D-Bus tray available:" << dbusTrayAvailable;
@@ -83,6 +92,20 @@ static bool isDBusTrayAvailable() {
}
#endif
+static QString mouseCursorTheme()
+{
+ static QString themeName = qEnvironmentVariable("XCURSOR_THEME");
+ return themeName;
+}
+
+static QSize mouseCursorSize()
+{
+ constexpr int defaultCursorSize = 24;
+ static const int xCursorSize = qEnvironmentVariableIntValue("XCURSOR_SIZE");
+ static const int s = xCursorSize > 0 ? xCursorSize : defaultCursorSize;
+ return QSize(s, s);
+}
+
#ifndef QT_NO_DBUS
static bool checkDBusGlobalMenuAvailable()
{
@@ -98,7 +121,288 @@ static bool isDBusGlobalMenuAvailable()
static bool dbusGlobalMenuAvailable = checkDBusGlobalMenuAvailable();
return dbusGlobalMenuAvailable;
}
+
+/*!
+ * \internal
+ * The QGenericUnixThemeDBusListener class listens to the SettingChanged DBus signal
+ * and translates it into combinations of the enums \c Provider and \c Setting.
+ * Upon construction, it logs success/failure of the DBus connection.
+ *
+ * The signal settingChanged delivers the normalized setting type and the new value as a string.
+ * It is emitted on known setting types only.
+ */
+
+class QGenericUnixThemeDBusListener : public QObject
+{
+ Q_OBJECT
+
+public:
+
+ enum class Provider {
+ Kde,
+ Gtk,
+ Gnome,
+ };
+ Q_ENUM(Provider)
+
+ enum class Setting {
+ Theme,
+ ApplicationStyle,
+ ColorScheme,
+ };
+ Q_ENUM(Setting)
+
+ QGenericUnixThemeDBusListener();
+ QGenericUnixThemeDBusListener(const QString &service, const QString &path,
+ const QString &interface, const QString &signal);
+
+private Q_SLOTS:
+ void onSettingChanged(const QString &location, const QString &key, const QDBusVariant &value);
+
+Q_SIGNALS:
+ void settingChanged(QGenericUnixThemeDBusListener::Provider provider,
+ QGenericUnixThemeDBusListener::Setting setting,
+ const QString &value);
+
+private:
+ struct DBusKey
+ {
+ QString location;
+ QString key;
+ DBusKey(const QString &loc, const QString &k) : location(loc), key(k) {};
+ bool operator<(const DBusKey &other) const
+ {
+ return location + key < other.location + other.key;
+ }
+ };
+
+ struct ChangeSignal
+ {
+ Provider provider;
+ Setting setting;
+ ChangeSignal(Provider p, Setting s) : provider(p), setting(s) {}
+ ChangeSignal() {}
+ };
+
+ // Json keys
+ static constexpr QLatin1StringView s_dbusLocation = QLatin1StringView("DBusLocation");
+ static constexpr QLatin1StringView s_dbusKey = QLatin1StringView("DBusKey");
+ static constexpr QLatin1StringView s_provider = QLatin1StringView("Provider");
+ static constexpr QLatin1StringView s_setting = QLatin1StringView("Setting");
+ static constexpr QLatin1StringView s_signals = QLatin1StringView("DbusSignals");
+ static constexpr QLatin1StringView s_root = QLatin1StringView("Qt.qpa.DBusSignals");
+
+ QFlatMap <DBusKey, ChangeSignal> m_signalMap;
+
+ void init(const QString &service, const QString &path,
+ const QString &interface, const QString &signal);
+
+ std::optional<ChangeSignal> findSignal(const QString &location, const QString &key) const;
+ void populateSignalMap();
+ void loadJson(const QString &fileName);
+ void saveJson(const QString &fileName) const;
+};
+
+QGenericUnixThemeDBusListener::QGenericUnixThemeDBusListener(const QString &service,
+ const QString &path, const QString &interface, const QString &signal)
+{
+ init (service, path, interface, signal);
+}
+
+QGenericUnixThemeDBusListener::QGenericUnixThemeDBusListener()
+{
+ static constexpr QLatin1StringView service("");
+ static constexpr QLatin1StringView path("/org/freedesktop/portal/desktop");
+ static constexpr QLatin1StringView interface("org.freedesktop.portal.Settings");
+ static constexpr QLatin1StringView signal("SettingChanged");
+
+ init (service, path, interface, signal);
+}
+
+void QGenericUnixThemeDBusListener::init(const QString &service, const QString &path,
+ const QString &interface, const QString &signal)
+{
+ QDBusConnection dbus = QDBusConnection::sessionBus();
+ const bool dBusRunning = dbus.isConnected();
+ bool dBusSignalConnected = false;
+#define LOG service << path << interface << signal;
+
+ if (dBusRunning) {
+ populateSignalMap();
+ qRegisterMetaType<QDBusVariant>();
+ dBusSignalConnected = dbus.connect(service, path, interface, signal, this,
+ SLOT(onSettingChanged(QString,QString,QDBusVariant)));
+ }
+
+ if (dBusSignalConnected) {
+ // Connection successful
+ qCDebug(lcQpaThemeDBus) << LOG;
+ } else {
+ if (dBusRunning) {
+ // DBus running, but connection failed
+ qCWarning(lcQpaThemeDBus) << "DBus connection failed:" << LOG;
+ } else {
+ // DBus not running
+ qCWarning(lcQpaThemeDBus) << "Session DBus not running.";
+ }
+ qCWarning(lcQpaThemeDBus) << "Application will not react to setting changes.\n"
+ << "Check your DBus installation.";
+ }
+#undef LOG
+}
+
+void QGenericUnixThemeDBusListener::loadJson(const QString &fileName)
+{
+ Q_ASSERT(!fileName.isEmpty());
+#define CHECK(cond, warning)\
+ if (!cond) {\
+ qCWarning(lcQpaThemeDBus) << fileName << warning << "Falling back to default.";\
+ return;\
+ }
+
+#define PARSE(var, enumeration, string)\
+ enumeration var;\
+ {\
+ bool success;\
+ const int val = QMetaEnum::fromType<enumeration>().keyToValue(string.toLatin1(), &success);\
+ CHECK(success, "Parse Error: Invalid value" << string << "for" << #var);\
+ var = static_cast<enumeration>(val);\
+ }
+
+ QFile file(fileName);
+ CHECK(file.exists(), fileName << "doesn't exist.");
+ CHECK(file.open(QIODevice::ReadOnly), "could not be opened for reading.");
+
+ QJsonParseError error;
+ QJsonDocument doc = QJsonDocument::fromJson(file.readAll(), &error);
+ CHECK((error.error == QJsonParseError::NoError), error.errorString());
+ CHECK(doc.isObject(), "Parse Error: Expected root object" << s_root);
+
+ const QJsonObject &root = doc.object();
+ CHECK(root.contains(s_root), "Parse Error: Expected root object" << s_root);
+ CHECK(root[s_root][s_signals].isArray(), "Parse Error: Expected array" << s_signals);
+
+ const QJsonArray &sigs = root[s_root][s_signals].toArray();
+ CHECK((sigs.count() > 0), "Parse Error: Found empty array" << s_signals);
+
+ for (auto sig = sigs.constBegin(); sig != sigs.constEnd(); ++sig) {
+ CHECK(sig->isObject(), "Parse Error: Expected object array" << s_signals);
+ const QJsonObject &obj = sig->toObject();
+ CHECK(obj.contains(s_dbusLocation), "Parse Error: Expected key" << s_dbusLocation);
+ CHECK(obj.contains(s_dbusKey), "Parse Error: Expected key" << s_dbusKey);
+ CHECK(obj.contains(s_provider), "Parse Error: Expected key" << s_provider);
+ CHECK(obj.contains(s_setting), "Parse Error: Expected key" << s_setting);
+ const QString &location = obj[s_dbusLocation].toString();
+ const QString &key = obj[s_dbusKey].toString();
+ const QString &providerString = obj[s_provider].toString();
+ const QString &settingString = obj[s_setting].toString();
+ PARSE(provider, Provider, providerString);
+ PARSE(setting, Setting, settingString);
+ const DBusKey dkey(location, key);
+ CHECK (!m_signalMap.contains(dkey), "Duplicate key" << location << key);
+ m_signalMap.insert(dkey, ChangeSignal(provider, setting));
+ }
+#undef PARSE
+#undef CHECK
+
+ if (m_signalMap.count() > 0)
+ qCInfo(lcQpaThemeDBus) << "Successfully imported" << fileName;
+ else
+ qCWarning(lcQpaThemeDBus) << "No data imported from" << fileName << "falling back to default.";
+
+#ifdef QT_DEBUG
+ const int count = m_signalMap.count();
+ if (count == 0)
+ return;
+
+ qCDebug(lcQpaThemeDBus) << "Listening to" << count << "signals:";
+ for (auto it = m_signalMap.constBegin(); it != m_signalMap.constEnd(); ++it) {
+ qDebug() << it.key().key << it.key().location << "mapped to"
+ << it.value().provider << it.value().setting;
+ }
+
#endif
+}
+
+void QGenericUnixThemeDBusListener::saveJson(const QString &fileName) const
+{
+ Q_ASSERT(!m_signalMap.isEmpty());
+ Q_ASSERT(!fileName.isEmpty());
+ QFile file(fileName);
+ if (!file.open(QIODevice::WriteOnly)) {
+ qCWarning(lcQpaThemeDBus) << fileName << "could not be opened for writing.";
+ return;
+ }
+
+ QJsonArray sigs;
+ for (auto sig = m_signalMap.constBegin(); sig != m_signalMap.constEnd(); ++sig) {
+ const DBusKey &dkey = sig.key();
+ const ChangeSignal &csig = sig.value();
+ QJsonObject obj;
+ obj[s_dbusLocation] = dkey.location;
+ obj[s_dbusKey] = dkey.key;
+ obj[s_provider] = QLatin1StringView(QMetaEnum::fromType<Provider>()
+ .valueToKey(static_cast<int>(csig.provider)));
+ obj[s_setting] = QLatin1StringView(QMetaEnum::fromType<Setting>()
+ .valueToKey(static_cast<int>(csig.setting)));
+ sigs.append(obj);
+ }
+ QJsonObject obj;
+ obj[s_signals] = sigs;
+ QJsonObject root;
+ root[s_root] = obj;
+ QJsonDocument doc(root);
+ file.write(doc.toJson());
+ file.close();
+}
+
+void QGenericUnixThemeDBusListener::populateSignalMap()
+{
+ m_signalMap.clear();
+ const QString &loadJsonFile = qEnvironmentVariable("QT_QPA_DBUS_SIGNALS");
+ if (!loadJsonFile.isEmpty())
+ loadJson(loadJsonFile);
+ if (!m_signalMap.isEmpty())
+ return;
+
+ m_signalMap.insert(DBusKey("org.kde.kdeglobals.KDE"_L1, "widgetStyle"_L1),
+ ChangeSignal(Provider::Kde, Setting::ApplicationStyle));
+
+ m_signalMap.insert(DBusKey("org.kde.kdeglobals.General"_L1, "ColorScheme"_L1),
+ ChangeSignal(Provider::Kde, Setting::Theme));
+
+ m_signalMap.insert(DBusKey("org.gnome.desktop.interface"_L1, "gtk-theme"_L1),
+ ChangeSignal(Provider::Gtk, Setting::Theme));
+
+ m_signalMap.insert(DBusKey("org.freedesktop.appearance"_L1, "color-scheme"_L1),
+ ChangeSignal(Provider::Gnome, Setting::ColorScheme));
+
+ const QString &saveJsonFile = qEnvironmentVariable("QT_QPA_DBUS_SIGNALS_SAVE");
+ if (!saveJsonFile.isEmpty())
+ saveJson(saveJsonFile);
+}
+
+std::optional<QGenericUnixThemeDBusListener::ChangeSignal>
+ QGenericUnixThemeDBusListener::findSignal(const QString &location, const QString &key) const
+{
+ const DBusKey dkey(location, key);
+ std::optional<QGenericUnixThemeDBusListener::ChangeSignal> ret;
+ if (m_signalMap.contains(dkey))
+ ret.emplace(m_signalMap.value(dkey));
+
+ return ret;
+}
+
+void QGenericUnixThemeDBusListener::onSettingChanged(const QString &location, const QString &key, const QDBusVariant &value)
+{
+ auto sig = findSignal(location, key);
+ if (!sig.has_value())
+ return;
+
+ emit settingChanged(sig.value().provider, sig.value().setting, value.variant().toString());
+}
+
+#endif //QT_NO_DBUS
class QGenericUnixThemePrivate : public QPlatformThemePrivate
{
@@ -198,6 +502,10 @@ QVariant QGenericUnixTheme::themeHint(ThemeHint hint) const
return QVariant(int(X11KeyboardScheme));
case QPlatformTheme::UiEffects:
return QVariant(int(HoverEffect));
+ case QPlatformTheme::MouseCursorTheme:
+ return QVariant(mouseCursorTheme());
+ case QPlatformTheme::MouseCursorSize:
+ return QVariant(mouseCursorSize());
default:
break;
}
@@ -231,11 +539,9 @@ static QIcon xdgFileIcon(const QFileInfo &fileInfo)
#if QT_CONFIG(settings)
class QKdeThemePrivate : public QPlatformThemePrivate
{
+
public:
- QKdeThemePrivate(const QStringList &kdeDirs, int kdeVersion)
- : kdeDirs(kdeDirs)
- , kdeVersion(kdeVersion)
- { }
+ QKdeThemePrivate(const QStringList &kdeDirs, int kdeVersion);
static QString kdeGlobals(const QString &kdeDir, int kdeVersion)
{
@@ -266,8 +572,66 @@ public:
int startDragDist = 10;
int startDragTime = 500;
int cursorBlinkRate = 1000;
+ Qt::ColorScheme m_colorScheme = Qt::ColorScheme::Unknown;
+ void updateColorScheme(const QString &themeName);
+
+#ifndef QT_NO_DBUS
+private:
+ std::unique_ptr<QGenericUnixThemeDBusListener> dbus;
+ bool initDbus();
+ void settingChangedHandler(QGenericUnixThemeDBusListener::Provider provider,
+ QGenericUnixThemeDBusListener::Setting setting,
+ const QString &value);
+#endif // QT_NO_DBUS
};
+#ifndef QT_NO_DBUS
+void QKdeThemePrivate::settingChangedHandler(QGenericUnixThemeDBusListener::Provider provider,
+ QGenericUnixThemeDBusListener::Setting setting,
+ const QString &value)
+{
+ if (provider != QGenericUnixThemeDBusListener::Provider::Kde)
+ return;
+
+ switch (setting) {
+ case QGenericUnixThemeDBusListener::Setting::ColorScheme:
+ qCDebug(lcQpaThemeDBus) << "KDE color theme changed to:" << value;
+ break;
+ case QGenericUnixThemeDBusListener::Setting::Theme:
+ qCDebug(lcQpaThemeDBus) << "KDE global theme changed to:" << value;
+ break;
+ case QGenericUnixThemeDBusListener::Setting::ApplicationStyle:
+ qCDebug(lcQpaThemeDBus) << "KDE application style changed to:" << value;
+ break;
+ }
+
+ refresh();
+}
+
+bool QKdeThemePrivate::initDbus()
+{
+ dbus.reset(new QGenericUnixThemeDBusListener());
+ Q_ASSERT(dbus);
+
+ // Wrap slot in a lambda to avoid inheriting QKdeThemePrivate from QObject
+ auto wrapper = [this](QGenericUnixThemeDBusListener::Provider provider,
+ QGenericUnixThemeDBusListener::Setting setting,
+ const QString &value) {
+ settingChangedHandler(provider, setting, value);
+ };
+
+ return QObject::connect(dbus.get(), &QGenericUnixThemeDBusListener::settingChanged, dbus.get(), wrapper);
+}
+#endif // QT_NO_DBUS
+
+QKdeThemePrivate::QKdeThemePrivate(const QStringList &kdeDirs, int kdeVersion)
+ : kdeDirs(kdeDirs), kdeVersion(kdeVersion)
+{
+#ifndef QT_NO_DBUS
+ initDbus();
+#endif // QT_NO_DBUS
+}
+
void QKdeThemePrivate::refresh()
{
resources.clear();
@@ -297,6 +661,14 @@ void QKdeThemePrivate::refresh()
styleNames.push_front(style);
}
+ const QVariant colorScheme = readKdeSetting(QStringLiteral("ColorScheme"), kdeDirs,
+ kdeVersion, kdeSettings);
+
+ if (colorScheme.isValid())
+ updateColorScheme(colorScheme.toString());
+ else
+ m_colorScheme = Qt::ColorScheme::Unknown;
+
const QVariant singleClickValue = readKdeSetting(QStringLiteral("KDE/SingleClick"), kdeDirs, kdeVersion, kdeSettings);
if (singleClickValue.isValid())
singleClick = singleClickValue.toBool();
@@ -368,6 +740,8 @@ void QKdeThemePrivate::refresh()
if (QFont *toolBarFont = kdeFont(readKdeSetting(QStringLiteral("toolBarFont"), kdeDirs, kdeVersion, kdeSettings)))
resources.fonts[QPlatformTheme::ToolButtonFont] = toolBarFont;
+ QWindowSystemInterface::handleThemeChange();
+
qCDebug(lcQpaFonts) << "default fonts: system" << resources.fonts[QPlatformTheme::SystemFont]
<< "fixed" << resources.fonts[QPlatformTheme::FixedFont];
qDeleteAll(kdeSettings);
@@ -556,6 +930,10 @@ QVariant QKdeTheme::themeHint(QPlatformTheme::ThemeHint hint) const
return QVariant(d->cursorBlinkRate);
case QPlatformTheme::UiEffects:
return QVariant(int(HoverEffect));
+ case QPlatformTheme::MouseCursorTheme:
+ return QVariant(mouseCursorTheme());
+ case QPlatformTheme::MouseCursorSize:
+ return QVariant(mouseCursorSize());
default:
break;
}
@@ -572,6 +950,48 @@ QIcon QKdeTheme::fileIcon(const QFileInfo &fileInfo, QPlatformTheme::IconOptions
#endif
}
+Qt::ColorScheme QKdeTheme::colorScheme() const
+{
+ return d_func()->m_colorScheme;
+}
+
+/*!
+ \internal
+ \brief QKdeTheme::updateColorScheme - guess and set appearance for unix themes.
+ KDE themes do not have an appearance property.
+ The key words "dark" or "light" should be part of the theme name.
+ This is, however, not a mandatory convention.
+
+ If \param themeName contains a key word, the respective appearance is set.
+ If it doesn't, the appearance is heuristically determined by comparing text and base color
+ of the system palette.
+ */
+void QKdeThemePrivate::updateColorScheme(const QString &themeName)
+{
+ if (themeName.contains(QLatin1StringView("light"), Qt::CaseInsensitive)) {
+ m_colorScheme = Qt::ColorScheme::Light;
+ return;
+ }
+ if (themeName.contains(QLatin1StringView("dark"), Qt::CaseInsensitive)) {
+ m_colorScheme = Qt::ColorScheme::Dark;
+ return;
+ }
+
+ if (systemPalette) {
+ if (systemPalette->text().color().lightness() < systemPalette->base().color().lightness()) {
+ m_colorScheme = Qt::ColorScheme::Light;
+ return;
+ }
+ if (systemPalette->text().color().lightness() > systemPalette->base().color().lightness()) {
+ m_colorScheme = Qt::ColorScheme::Dark;
+ return;
+ }
+ }
+
+ m_colorScheme = Qt::ColorScheme::Unknown;
+}
+
+
const QPalette *QKdeTheme::palette(Palette type) const
{
Q_D(const QKdeTheme);
@@ -672,8 +1092,8 @@ const char *QGnomeTheme::name = "gnome";
class QGnomeThemePrivate : public QPlatformThemePrivate
{
public:
- QGnomeThemePrivate() : systemFont(nullptr), fixedFont(nullptr) {}
- ~QGnomeThemePrivate() { delete systemFont; delete fixedFont; }
+ QGnomeThemePrivate();
+ ~QGnomeThemePrivate();
void configureFonts(const QString &gtkFontName) const
{
@@ -688,10 +1108,70 @@ public:
qCDebug(lcQpaFonts) << "default fonts: system" << systemFont << "fixed" << fixedFont;
}
- mutable QFont *systemFont;
- mutable QFont *fixedFont;
+ mutable QFont *systemFont = nullptr;
+ mutable QFont *fixedFont = nullptr;
+
+#ifndef QT_NO_DBUS
+ Qt::ColorScheme m_colorScheme = Qt::ColorScheme::Unknown;
+private:
+ std::unique_ptr<QGenericUnixThemeDBusListener> dbus;
+ bool initDbus();
+ void updateColorScheme(const QString &themeName);
+#endif // QT_NO_DBUS
};
+QGnomeThemePrivate::QGnomeThemePrivate()
+{
+#ifndef QT_NO_DBUS
+ initDbus();
+#endif // QT_NO_DBUS
+}
+QGnomeThemePrivate::~QGnomeThemePrivate()
+{
+ if (systemFont)
+ delete systemFont;
+ if (fixedFont)
+ delete fixedFont;
+}
+
+#ifndef QT_NO_DBUS
+bool QGnomeThemePrivate::initDbus()
+{
+ dbus.reset(new QGenericUnixThemeDBusListener());
+ Q_ASSERT(dbus);
+
+ // Wrap slot in a lambda to avoid inheriting QGnomeThemePrivate from QObject
+ auto wrapper = [this](QGenericUnixThemeDBusListener::Provider provider,
+ QGenericUnixThemeDBusListener::Setting setting,
+ const QString &value) {
+ if (provider != QGenericUnixThemeDBusListener::Provider::Gnome
+ && provider != QGenericUnixThemeDBusListener::Provider::Gtk) {
+ return;
+ }
+
+ if (setting == QGenericUnixThemeDBusListener::Setting::Theme)
+ updateColorScheme(value);
+ };
+
+ return QObject::connect(dbus.get(), &QGenericUnixThemeDBusListener::settingChanged, dbus.get(), wrapper);
+}
+
+void QGnomeThemePrivate::updateColorScheme(const QString &themeName)
+{
+ const auto oldColorScheme = m_colorScheme;
+ if (themeName.contains(QLatin1StringView("light"), Qt::CaseInsensitive)) {
+ m_colorScheme = Qt::ColorScheme::Light;
+ } else if (themeName.contains(QLatin1StringView("dark"), Qt::CaseInsensitive)) {
+ m_colorScheme = Qt::ColorScheme::Dark;
+ } else {
+ m_colorScheme = Qt::ColorScheme::Unknown;
+ }
+
+ if (oldColorScheme != m_colorScheme)
+ QWindowSystemInterface::handleThemeChange();
+}
+#endif // QT_NO_DBUS
+
QGnomeTheme::QGnomeTheme()
: QPlatformTheme(new QGnomeThemePrivate())
{
@@ -726,6 +1206,12 @@ QVariant QGnomeTheme::themeHint(QPlatformTheme::ThemeHint hint) const
case QPlatformTheme::ButtonPressKeys:
return QVariant::fromValue(
QList<Qt::Key>({ Qt::Key_Space, Qt::Key_Return, Qt::Key_Enter, Qt::Key_Select }));
+ case QPlatformTheme::PreselectFirstFileInDirectory:
+ return true;
+ case QPlatformTheme::MouseCursorTheme:
+ return QVariant(mouseCursorTheme());
+ case QPlatformTheme::MouseCursorSize:
+ return QVariant(mouseCursorSize());
default:
break;
}
@@ -769,6 +1255,12 @@ QPlatformMenuBar *QGnomeTheme::createPlatformMenuBar() const
return new QDBusMenuBar();
return nullptr;
}
+
+Qt::ColorScheme QGnomeTheme::colorScheme() const
+{
+ return d_func()->m_colorScheme;
+}
+
#endif
#if !defined(QT_NO_DBUS) && !defined(QT_NO_SYSTEMTRAYICON)
@@ -853,3 +1345,7 @@ QStringList QGenericUnixTheme::themeNames()
}
QT_END_NAMESPACE
+
+#ifndef QT_NO_DBUS
+#include "qgenericunixthemes.moc"
+#endif // QT_NO_DBUS
diff --git a/src/gui/platform/unix/qgenericunixthemes_p.h b/src/gui/platform/unix/qgenericunixthemes_p.h
index f5f60865aa..63b20651e6 100644
--- a/src/gui/platform/unix/qgenericunixthemes_p.h
+++ b/src/gui/platform/unix/qgenericunixthemes_p.h
@@ -77,6 +77,7 @@ public:
QPlatformTheme::IconOptions iconOptions = { }) const override;
const QPalette *palette(Palette type = SystemPalette) const override;
+ Qt::ColorScheme colorScheme() const override;
const QFont *font(Font type) const override;
#ifndef QT_NO_DBUS
@@ -106,6 +107,7 @@ public:
virtual QString gtkFontName() const;
#ifndef QT_NO_DBUS
QPlatformMenuBar *createPlatformMenuBar() const override;
+ Qt::ColorScheme colorScheme() const override;
#endif
#if !defined(QT_NO_DBUS) && !defined(QT_NO_SYSTEMTRAYICON)
QPlatformSystemTrayIcon *createPlatformSystemTrayIcon() const override;
diff --git a/src/gui/platform/unix/qunixnativeinterface.cpp b/src/gui/platform/unix/qunixnativeinterface.cpp
index 4ea88a590b..09561d9ada 100644
--- a/src/gui/platform/unix/qunixnativeinterface.cpp
+++ b/src/gui/platform/unix/qunixnativeinterface.cpp
@@ -3,7 +3,9 @@
#include <QtGui/private/qtguiglobal_p.h>
-#include <QtGui/private/qopenglcontext_p.h>
+#if QT_CONFIG(opengl)
+# include <QtGui/private/qopenglcontext_p.h>
+#endif
#include <QtGui/private/qguiapplication_p.h>
#include <qpa/qplatformopenglcontext.h>
@@ -121,6 +123,22 @@ QOpenGLContext *QNativeInterface::QGLXContext::fromNative(GLXContext visualBased
\return the EGLDisplay associated with the underlying EGLContext.
*/
+
+/*!
+ \fn void QNativeInterface::QEGLContext::invalidateContext()
+ \since 6.5
+ \brief Marks the context as invalid
+
+ If this context is used by the Qt Quick scenegraph, this will trigger the
+ SceneGraph to destroy this context and create a new one.
+
+ Similarly to QPlatformWindow::invalidateSurface(),
+ this function can only be expected to have an effect on certain platforms,
+ such as eglfs.
+
+ \sa QOpenGLContext::isValid(), QPlatformWindow::invalidateSurface()
+*/
+
QT_DEFINE_NATIVE_INTERFACE(QEGLContext);
QT_DEFINE_PRIVATE_NATIVE_INTERFACE(QEGLIntegration);
@@ -213,4 +231,81 @@ QT_DEFINE_PRIVATE_NATIVE_INTERFACE(QEvdevKeyMapper);
#endif // QT_CONFIG(evdev)
+#if QT_CONFIG(wayland)
+
+/*!
+ \class QNativeInterface::QWaylandApplication
+ \inheaderfile QGuiApplication
+ \since 6.5
+ \brief Native interface to a Wayland application.
+
+ Accessed through QGuiApplication::nativeInterface().
+ \inmodule QtGui
+ \ingroup native-interfaces
+ \ingroup native-interfaces-qguiapplication
+*/
+/*!
+ \fn wl_display *QNativeInterface::QWaylandApplication::display() const
+ \return the wl_display that the application is using.
+*/
+/*!
+ \fn wl_compositor *QNativeInterface::QWaylandApplication::compositor() const
+ \return the wl_compositor that the application is using.
+*/
+/*!
+ \fn wl_keyboard *QNativeInterface::QWaylandApplication::keyboard() const
+ \return the wl_keyboard belonging to seat() if available.
+*/
+/*!
+ \fn wl_pointer *QNativeInterface::QWaylandApplication::pointer() const
+ \return the wl_pointer belonging to seat() if available.
+*/
+/*!
+ \fn wl_touch *QNativeInterface::QWaylandApplication::touch() const
+ \return the wl_touch belonging to seat() if available.
+*/
+/*!
+ \fn uint *QNativeInterface::QWaylandApplication::lastInputSerial() const
+ \return the serial of the last input event on any seat.
+*/
+/*!
+ \fn wl_seat *QNativeInterface::QWaylandApplication::lastInputSeat() const
+ \return the seat on which the last input event happened.
+*/
+/*!
+ \fn wl_seat *QNativeInterface::QWaylandApplication::seat() const
+ \return the seat associated with the default input device.
+*/
+
+QT_DEFINE_NATIVE_INTERFACE(QWaylandApplication);
+
+/*!
+ \class QNativeInterface::QWaylandScreen
+ \since 6.7
+ \brief Native interface to a screen on Wayland.
+
+ Accessed through QScreen::nativeInterface().
+ \inmodule QtGui
+ \ingroup native-interfaces
+ \ingroup native-interfaces-qscreen
+*/
+/*!
+ \fn wl_output *QNativeInterface::QWaylandScreen::output() const
+ \return the underlying wl_output of this QScreen.
+*/
+QT_DEFINE_NATIVE_INTERFACE(QWaylandScreen);
+
+/*!
+ \class QNativeInterface::QWaylandWindow
+ \since 6.5
+ \internal
+ \brief Native interface to a Wayland window.
+ \inmodule QtGui
+ \ingroup native-interfaces
+*/
+
+QT_DEFINE_PRIVATE_NATIVE_INTERFACE(QWaylandWindow);
+
+#endif // QT_CONFIG(wayland)
+
QT_END_NAMESPACE
diff --git a/src/gui/platform/unix/qxkbcommon.cpp b/src/gui/platform/unix/qxkbcommon.cpp
index b3ee0f4948..ed29db3005 100644
--- a/src/gui/platform/unix/qxkbcommon.cpp
+++ b/src/gui/platform/unix/qxkbcommon.cpp
@@ -17,8 +17,6 @@
QT_BEGIN_NAMESPACE
-Q_LOGGING_CATEGORY(lcXkbcommon, "qt.xkbcommon")
-
static int keysymToQtKey_internal(xkb_keysym_t keysym, Qt::KeyboardModifiers modifiers,
xkb_state *state, xkb_keycode_t code,
bool superAsMeta, bool hyperAsMeta);
@@ -239,10 +237,14 @@ static constexpr const auto KeyTbl = qMakeArray(
Xkb2Qt<XKB_KEY_dead_small_schwa, Qt::Key_Dead_Small_Schwa>,
Xkb2Qt<XKB_KEY_dead_capital_schwa, Qt::Key_Dead_Capital_Schwa>,
Xkb2Qt<XKB_KEY_dead_greek, Qt::Key_Dead_Greek>,
+/* The following four XKB_KEY_dead keys got removed in libxkbcommon 1.6.0
+ The define check is kind of version check here. */
+#ifdef XKB_KEY_dead_lowline
Xkb2Qt<XKB_KEY_dead_lowline, Qt::Key_Dead_Lowline>,
Xkb2Qt<XKB_KEY_dead_aboveverticalline, Qt::Key_Dead_Aboveverticalline>,
Xkb2Qt<XKB_KEY_dead_belowverticalline, Qt::Key_Dead_Belowverticalline>,
Xkb2Qt<XKB_KEY_dead_longsolidusoverlay, Qt::Key_Dead_Longsolidusoverlay>,
+#endif
// Special keys from X.org - This include multimedia keys,
// wireless/bluetooth/uwb keys, special launcher keys, etc.
@@ -298,6 +300,7 @@ static constexpr const auto KeyTbl = qMakeArray(
Xkb2Qt<XKB_KEY_XF86Book, Qt::Key_Book>,
Xkb2Qt<XKB_KEY_XF86CD, Qt::Key_CD>,
Xkb2Qt<XKB_KEY_XF86Calculater, Qt::Key_Calculator>,
+ Xkb2Qt<XKB_KEY_XF86Calculator, Qt::Key_Calculator>,
Xkb2Qt<XKB_KEY_XF86Clear, Qt::Key_Clear>,
Xkb2Qt<XKB_KEY_XF86ClearGrab, Qt::Key_ClearGrab>,
Xkb2Qt<XKB_KEY_XF86Close, Qt::Key_Close>,
@@ -461,7 +464,7 @@ QList<xkb_keysym_t> QXkbCommon::toKeysym(QKeyEvent *event)
// From libxkbcommon keysym-utf.c:
// "We allow to represent any UCS character in the range U-00000000 to
// U-00FFFFFF by a keysym value in the range 0x01000000 to 0x01ffffff."
- for (uint utf32 : qAsConst(ucs4))
+ for (uint utf32 : std::as_const(ucs4))
keysyms.append(utf32 | 0x01000000);
return keysyms;
@@ -512,13 +515,13 @@ static int keysymToQtKey_internal(xkb_keysym_t keysym, Qt::KeyboardModifiers mod
// numeric keypad keys
qtKey = Qt::Key_0 + (keysym - XKB_KEY_KP_0);
} else if (QXkbCommon::isLatin1(keysym)) {
- // Upper-case first, since Qt::Keys are defined in terms of their
- // upper-case versions.
+ // Most Qt::Key values are determined by their upper-case version,
+ // where this is in the Latin-1 repertoire. So start with that:
qtKey = QXkbCommon::qxkbcommon_xkb_keysym_to_upper(keysym);
- // Upper-casing a Latin1 character might move it out of Latin1 range,
- // for example U+00B5 MICRO SIGN, which upper-case equivalent is
- // U+039C GREEK CAPITAL LETTER MU. If that's the case, then map the
- // original lower-case character.
+ // However, Key_mu and Key_ydiaeresis are U+00B5 MICRO SIGN and
+ // U+00FF LATIN SMALL LETTER Y WITH DIAERESIS, both lower-case,
+ // with upper-case forms outside Latin-1, so use them as they are
+ // since they're the Qt::Key values.
if (!QXkbCommon::isLatin1(qtKey))
qtKey = keysym;
} else {
@@ -564,7 +567,7 @@ static int keysymToQtKey_internal(xkb_keysym_t keysym, Qt::KeyboardModifiers mod
return qtKey;
}
-Qt::KeyboardModifiers QXkbCommon::modifiers(struct xkb_state *state)
+Qt::KeyboardModifiers QXkbCommon::modifiers(struct xkb_state *state, xkb_keysym_t keysym)
{
Qt::KeyboardModifiers modifiers = Qt::NoModifier;
@@ -577,6 +580,9 @@ Qt::KeyboardModifiers QXkbCommon::modifiers(struct xkb_state *state)
if (xkb_state_mod_name_is_active(state, XKB_MOD_NAME_LOGO, XKB_STATE_MODS_EFFECTIVE) > 0)
modifiers |= Qt::MetaModifier;
+ if (isKeypad(keysym))
+ modifiers |= Qt::KeypadModifier;
+
return modifiers;
}
@@ -593,10 +599,24 @@ static const Qt::KeyboardModifiers ModsTbl[] = {
Qt::NoModifier // Fall-back to raw Key_*, for non-latin1 kb layouts
};
+/*
+ Compatibility until all sub modules have transitioned to new API below
+*/
QList<int> QXkbCommon::possibleKeys(xkb_state *state, const QKeyEvent *event,
bool superAsMeta, bool hyperAsMeta)
{
QList<int> result;
+ auto keyCombinations = possibleKeyCombinations(state, event, superAsMeta, hyperAsMeta);
+ for (auto keyCombination : keyCombinations)
+ result << keyCombination.toCombined();
+
+ return result;
+}
+
+QList<QKeyCombination> QXkbCommon::possibleKeyCombinations(xkb_state *state, const QKeyEvent *event,
+ bool superAsMeta, bool hyperAsMeta)
+{
+ QList<QKeyCombination> result;
quint32 keycode = event->nativeScanCode();
if (!keycode)
return result;
@@ -610,7 +630,7 @@ QList<int> QXkbCommon::possibleKeys(xkb_state *state, const QKeyEvent *event,
ScopedXKBState scopedXkbQueryState(xkb_state_new(keymap));
xkb_state *queryState = scopedXkbQueryState.get();
if (!queryState) {
- qCWarning(lcXkbcommon) << Q_FUNC_INFO << "failed to compile xkb keymap";
+ qCWarning(lcQpaKeyMapper) << Q_FUNC_INFO << "failed to compile xkb keymap";
return result;
}
// get kb state from the master state and update the temporary state
@@ -636,7 +656,7 @@ QList<int> QXkbCommon::possibleKeys(xkb_state *state, const QKeyEvent *event,
int baseQtKey = keysymToQtKey_internal(sym, modifiers, queryState, keycode, superAsMeta, hyperAsMeta);
if (baseQtKey)
- result += (baseQtKey + int(modifiers));
+ result += QKeyCombination::fromCombined(baseQtKey + int(modifiers));
xkb_mod_index_t shiftMod = xkb_keymap_mod_get_index(keymap, "Shift");
xkb_mod_index_t altMod = xkb_keymap_mod_get_index(keymap, "Alt");
@@ -682,8 +702,9 @@ QList<int> QXkbCommon::possibleKeys(xkb_state *state, const QKeyEvent *event,
// catch only more specific shortcuts, i.e. Ctrl+Shift+= also generates Ctrl++ and +,
// but Ctrl++ is more specific than +, so we should skip the last one
bool ambiguous = false;
- for (int shortcut : qAsConst(result)) {
- if (int(shortcut & ~Qt::KeyboardModifierMask) == qtKey && (shortcut & mods) == mods) {
+ for (auto keyCombination : std::as_const(result)) {
+ if (keyCombination.key() == qtKey
+ && (keyCombination.keyboardModifiers() & mods) == mods) {
ambiguous = true;
break;
}
@@ -691,7 +712,7 @@ QList<int> QXkbCommon::possibleKeys(xkb_state *state, const QKeyEvent *event,
if (ambiguous)
continue;
- result += (qtKey + int(mods));
+ result += QKeyCombination::fromCombined(qtKey + int(mods));
}
}
@@ -723,13 +744,15 @@ void QXkbCommon::verifyHasLatinLayout(xkb_keymap *keymap)
// selected layouts is irrelevant. Properly functioning desktop environments
// handle this behind the scenes, even if no latin key based layout has been
// explicitly listed in the selected layouts.
- qCDebug(lcXkbcommon, "no keyboard layouts with latin keys present");
+ qCDebug(lcQpaKeyMapper, "no keyboard layouts with latin keys present");
}
xkb_keysym_t QXkbCommon::lookupLatinKeysym(xkb_state *state, xkb_keycode_t keycode)
{
xkb_layout_index_t layout;
xkb_keysym_t sym = XKB_KEY_NoSymbol;
+ if (!state)
+ return sym;
xkb_keymap *keymap = xkb_state_get_keymap(state);
const xkb_layout_index_t layoutCount = xkb_keymap_num_layouts_for_key(keymap, keycode);
const xkb_layout_index_t currentLayout = xkb_state_key_get_layout(state, keycode);
@@ -795,7 +818,7 @@ void QXkbCommon::setXkbContext(QPlatformInputContext *inputContext, struct xkb_c
QMetaMethod method = inputContext->metaObject()->method(methodIndex);
Q_ASSERT(method.isValid());
if (!method.isValid())
- qCWarning(lcXkbcommon) << normalizedSignature << "not found on" << inputContextClassName;
+ qCWarning(lcQpaKeyMapper) << normalizedSignature << "not found on" << inputContextClassName;
return method;
}();
diff --git a/src/gui/platform/unix/qxkbcommon_p.h b/src/gui/platform/unix/qxkbcommon_p.h
index adc96b2ad4..a40d794451 100644
--- a/src/gui/platform/unix/qxkbcommon_p.h
+++ b/src/gui/platform/unix/qxkbcommon_p.h
@@ -23,12 +23,12 @@
#include <xkbcommon/xkbcommon.h>
+#include <qpa/qplatformkeymapper.h>
+
#include <memory>
QT_BEGIN_NAMESPACE
-Q_DECLARE_LOGGING_CATEGORY(lcXkbcommon)
-
class QEvent;
class QKeyEvent;
class QPlatformInputContext;
@@ -44,26 +44,67 @@ public:
static int keysymToQtKey(xkb_keysym_t keysym, Qt::KeyboardModifiers modifiers);
static int keysymToQtKey(xkb_keysym_t keysym, Qt::KeyboardModifiers modifiers,
xkb_state *state, xkb_keycode_t code,
- bool superAsMeta = false, bool hyperAsMeta = false);
+ bool superAsMeta = true, bool hyperAsMeta = true);
// xkbcommon_* API is part of libxkbcommon internals, with modifications as
// described in the header of the implementation file.
static void xkbcommon_XConvertCase(xkb_keysym_t sym, xkb_keysym_t *lower, xkb_keysym_t *upper);
static xkb_keysym_t qxkbcommon_xkb_keysym_to_upper(xkb_keysym_t ks);
- static Qt::KeyboardModifiers modifiers(struct xkb_state *state);
+ static Qt::KeyboardModifiers modifiers(struct xkb_state *state, xkb_keysym_t keysym = XKB_KEY_VoidSymbol);
- static QList<int> possibleKeys(xkb_state *state, const QKeyEvent *event,
- bool superAsMeta = false, bool hyperAsMeta = false);
+ static QList<int> possibleKeys(xkb_state *state,
+ const QKeyEvent *event, bool superAsMeta = false, bool hyperAsMeta = false);
+ static QList<QKeyCombination> possibleKeyCombinations(xkb_state *state,
+ const QKeyEvent *event, bool superAsMeta = false, bool hyperAsMeta = false);
static void verifyHasLatinLayout(xkb_keymap *keymap);
static xkb_keysym_t lookupLatinKeysym(xkb_state *state, xkb_keycode_t keycode);
static bool isLatin1(xkb_keysym_t sym) {
- return sym <= 0xff;
+ return sym >= 0x20 && sym <= 0xff;
}
static bool isKeypad(xkb_keysym_t sym) {
- return sym >= XKB_KEY_KP_Space && sym <= XKB_KEY_KP_9;
+ switch (sym) {
+ case XKB_KEY_KP_Space:
+ case XKB_KEY_KP_Tab:
+ case XKB_KEY_KP_Enter:
+ case XKB_KEY_KP_F1:
+ case XKB_KEY_KP_F2:
+ case XKB_KEY_KP_F3:
+ case XKB_KEY_KP_F4:
+ case XKB_KEY_KP_Home:
+ case XKB_KEY_KP_Left:
+ case XKB_KEY_KP_Up:
+ case XKB_KEY_KP_Right:
+ case XKB_KEY_KP_Down:
+ case XKB_KEY_KP_Prior:
+ case XKB_KEY_KP_Next:
+ case XKB_KEY_KP_End:
+ case XKB_KEY_KP_Begin:
+ case XKB_KEY_KP_Insert:
+ case XKB_KEY_KP_Delete:
+ case XKB_KEY_KP_Equal:
+ case XKB_KEY_KP_Multiply:
+ case XKB_KEY_KP_Add:
+ case XKB_KEY_KP_Separator:
+ case XKB_KEY_KP_Subtract:
+ case XKB_KEY_KP_Decimal:
+ case XKB_KEY_KP_Divide:
+ case XKB_KEY_KP_0:
+ case XKB_KEY_KP_1:
+ case XKB_KEY_KP_2:
+ case XKB_KEY_KP_3:
+ case XKB_KEY_KP_4:
+ case XKB_KEY_KP_5:
+ case XKB_KEY_KP_6:
+ case XKB_KEY_KP_7:
+ case XKB_KEY_KP_8:
+ case XKB_KEY_KP_9:
+ return true;
+ default:
+ return false;
+ }
}
static void setXkbContext(QPlatformInputContext *inputContext, struct xkb_context *context);
diff --git a/src/gui/platform/wasm/qlocalfileapi.cpp b/src/gui/platform/wasm/qlocalfileapi.cpp
index ec7ae40391..76b99361c4 100644
--- a/src/gui/platform/wasm/qlocalfileapi.cpp
+++ b/src/gui/platform/wasm/qlocalfileapi.cpp
@@ -8,29 +8,62 @@
QT_BEGIN_NAMESPACE
namespace LocalFileApi {
namespace {
+std::string qtFilterListToFileInputAccept(const QStringList &filterList)
+{
+ QStringList transformed;
+ for (const auto &filter : filterList) {
+ const auto type = Type::fromQt(filter);
+ if (type && type->accept()) {
+ const auto &extensions = type->accept()->mimeType().extensions();
+ std::transform(extensions.begin(), extensions.end(), std::back_inserter(transformed),
+ [](const Type::Accept::MimeType::Extension &extension) {
+ return extension.value().toString();
+ });
+ }
+ }
+ return transformed.join(QStringLiteral(",")).toStdString();
+}
+
std::optional<emscripten::val> qtFilterListToTypes(const QStringList &filterList)
{
using namespace qstdweb;
using namespace emscripten;
-
auto types = emscripten::val::array();
for (const auto &fileFilter : filterList) {
auto type = Type::fromQt(fileFilter);
- if (type)
- types.call<void>("push", type->asVal());
+ if (type) {
+ auto jsType = emscripten::val::object();
+ jsType.set("description", type->description().toString().toStdString());
+ if (type->accept()) {
+ jsType.set("accept", ([&mimeType = type->accept()->mimeType()]() {
+ val acceptDict = val::object();
+
+ QList<emscripten::val> extensions;
+ extensions.reserve(mimeType.extensions().size());
+ std::transform(
+ mimeType.extensions().begin(), mimeType.extensions().end(),
+ std::back_inserter(extensions),
+ [](const Type::Accept::MimeType::Extension &extension) {
+ return val(extension.value().toString().toStdString());
+ });
+ acceptDict.set("application/octet-stream",
+ emscripten::val::array(extensions.begin(),
+ extensions.end()));
+ return acceptDict;
+ })());
+ }
+ types.call<void>("push", std::move(jsType));
+ }
}
return types["length"].as<int>() == 0 ? std::optional<emscripten::val>() : types;
}
-}
+} // namespace
Type::Type(QStringView description, std::optional<Accept> accept)
- : m_storage(emscripten::val::object())
+ : m_description(description.trimmed()), m_accept(std::move(accept))
{
- m_storage.set("description", description.trimmed().toString().toStdString());
- if (accept)
- m_storage.set("accept", accept->asVal());
}
Type::~Type() = default;
@@ -46,7 +79,7 @@ std::optional<Type> Type::fromQt(QStringView type)
// Group 1 is treated as the description, whereas group 2 or 3 are treated as the filter list.
static QRegularExpression regex(
QString(QStringLiteral("(?:(?:([^(]*)\\(([^()]+)\\)[^)]*)|([^()]+))")));
- const auto match = regex.match(type);
+ const auto match = regex.matchView(type);
if (!match.hasMatch())
return std::nullopt;
@@ -69,12 +102,7 @@ std::optional<Type> Type::fromQt(QStringView type)
return Type(description, std::move(*accept));
}
-emscripten::val Type::asVal() const
-{
- return m_storage;
-}
-
-Type::Accept::Accept() : m_storage(emscripten::val::object()) { }
+Type::Accept::Accept() = default;
Type::Accept::~Accept() = default;
@@ -86,7 +114,7 @@ std::optional<Type::Accept> Type::Accept::fromQt(QStringView qtRepresentation)
// The next group of non-empty characters.
static QRegularExpression internalRegex(QString(QStringLiteral("([^\\s]+)\\s*")));
int offset = 0;
- auto internalMatch = internalRegex.match(qtRepresentation, offset);
+ auto internalMatch = internalRegex.matchView(qtRepresentation, offset);
MimeType mimeType;
while (internalMatch.hasMatch()) {
@@ -97,42 +125,28 @@ std::optional<Type::Accept> Type::Accept::fromQt(QStringView qtRepresentation)
mimeType.addExtension(*webExtension);
- internalMatch = internalRegex.match(qtRepresentation, internalMatch.capturedEnd());
+ internalMatch = internalRegex.matchView(qtRepresentation, internalMatch.capturedEnd());
}
- accept.addMimeType(mimeType);
+ accept.setMimeType(mimeType);
return accept;
}
-void Type::Accept::addMimeType(MimeType mimeType)
-{
- // The mime type provided here does not seem to have any effect at the result at all.
- m_storage.set("application/octet-stream", mimeType.asVal());
-}
-
-emscripten::val Type::Accept::asVal() const
+void Type::Accept::setMimeType(MimeType mimeType)
{
- return m_storage;
+ m_mimeType = std::move(mimeType);
}
-Type::Accept::MimeType::MimeType() : m_storage(emscripten::val::array()) { }
+Type::Accept::MimeType::MimeType() = default;
Type::Accept::MimeType::~MimeType() = default;
void Type::Accept::MimeType::addExtension(Extension extension)
{
- m_storage.call<void>("push", extension.asVal());
+ m_extensions.push_back(std::move(extension));
}
-emscripten::val Type::Accept::MimeType::asVal() const
-{
- return m_storage;
-}
-
-Type::Accept::MimeType::Extension::Extension(QStringView extension)
- : m_storage(extension.toString().toStdString())
-{
-}
+Type::Accept::MimeType::Extension::Extension(QStringView extension) : m_value(extension) { }
Type::Accept::MimeType::Extension::~Extension() = default;
@@ -144,7 +158,7 @@ Type::Accept::MimeType::Extension::fromQt(QStringView qtRepresentation)
// The web filter does not support wildcards.
static QRegularExpression qtAcceptAllRegex(
QRegularExpression::anchoredPattern(QString(QStringLiteral("[*]+|[*]+\\.[*]+"))));
- if (qtAcceptAllRegex.match(qtRepresentation).hasMatch())
+ if (qtAcceptAllRegex.matchView(qtRepresentation).hasMatch())
return std::nullopt;
// Checks for correctness. The web filter only allows filename extensions and does not filter
@@ -153,7 +167,7 @@ Type::Accept::MimeType::Extension::fromQt(QStringView qtRepresentation)
static QRegularExpression qtFilenameMatcherRegex(
QRegularExpression::anchoredPattern(QString(QStringLiteral("(\\*?)(\\.[^*]+)"))));
- auto extensionMatch = qtFilenameMatcherRegex.match(qtRepresentation);
+ auto extensionMatch = qtFilenameMatcherRegex.matchView(qtRepresentation);
if (extensionMatch.hasMatch())
return Extension(extensionMatch.capturedView(2));
@@ -161,15 +175,10 @@ Type::Accept::MimeType::Extension::fromQt(QStringView qtRepresentation)
return std::nullopt;
}
-emscripten::val Type::Accept::MimeType::Extension::asVal() const
-{
- return m_storage;
-}
-
emscripten::val makeOpenFileOptions(const QStringList &filterList, bool acceptMultiple)
{
auto options = emscripten::val::object();
- if (auto typeList = LocalFileApi::qtFilterListToTypes(filterList)) {
+ if (auto typeList = qtFilterListToTypes(filterList); typeList) {
options.set("types", std::move(*typeList));
options.set("excludeAcceptAllOption", true);
}
@@ -186,12 +195,17 @@ emscripten::val makeSaveFileOptions(const QStringList &filterList, const std::st
if (!suggestedName.empty())
options.set("suggestedName", emscripten::val(suggestedName));
- if (auto typeList = LocalFileApi::qtFilterListToTypes(filterList))
+ if (auto typeList = qtFilterListToTypes(filterList))
options.set("types", emscripten::val(std::move(*typeList)));
return options;
}
-} // namespace LocalFileApi
+std::string makeFileInputAccept(const QStringList &filterList)
+{
+ return qtFilterListToFileInputAccept(filterList);
+}
+
+} // namespace LocalFileApi
QT_END_NAMESPACE
diff --git a/src/gui/platform/wasm/qlocalfileapi_p.h b/src/gui/platform/wasm/qlocalfileapi_p.h
index a8e7f666f9..1398d674d8 100644
--- a/src/gui/platform/wasm/qlocalfileapi_p.h
+++ b/src/gui/platform/wasm/qlocalfileapi_p.h
@@ -24,7 +24,8 @@
QT_BEGIN_NAMESPACE
namespace LocalFileApi {
-class Q_CORE_EXPORT Type {
+class Q_AUTOTEST_EXPORT Type
+{
public:
class Accept {
public:
@@ -36,12 +37,12 @@ public:
~Extension();
- emscripten::val asVal() const;
+ const QStringView &value() const { return m_value; }
private:
explicit Extension(QStringView extension);
- emscripten::val m_storage;
+ QStringView m_value;
};
MimeType();
@@ -49,37 +50,43 @@ public:
void addExtension(Extension type);
- emscripten::val asVal() const;
+ const std::vector<Extension> &extensions() const { return m_extensions; }
private:
- emscripten::val m_storage;
+ std::vector<Extension> m_extensions;
};
static std::optional<Accept> fromQt(QStringView type);
~Accept();
- void addMimeType(MimeType mimeType);
+ void setMimeType(MimeType mimeType);
- emscripten::val asVal() const;
+ const MimeType &mimeType() const { return m_mimeType; }
private:
Accept();
- emscripten::val m_storage;
+ MimeType m_mimeType;
};
Type(QStringView description, std::optional<Accept> accept);
~Type();
static std::optional<Type> fromQt(QStringView type);
- emscripten::val asVal() const;
+ const QStringView &description() const { return m_description; }
+ const std::optional<Accept> &accept() const { return m_accept; }
private:
- emscripten::val m_storage;
+ QStringView m_description;
+ std::optional<Accept> m_accept;
};
-Q_CORE_EXPORT emscripten::val makeOpenFileOptions(const QStringList &filterList, bool acceptMultiple);
-Q_CORE_EXPORT emscripten::val makeSaveFileOptions(const QStringList &filterList, const std::string& suggestedName);
+Q_AUTOTEST_EXPORT emscripten::val makeOpenFileOptions(const QStringList &filterList,
+ bool acceptMultiple);
+Q_AUTOTEST_EXPORT emscripten::val makeSaveFileOptions(const QStringList &filterList,
+ const std::string &suggestedName);
+
+Q_AUTOTEST_EXPORT std::string makeFileInputAccept(const QStringList &filterList);
} // namespace LocalFileApi
QT_END_NAMESPACE
diff --git a/src/gui/platform/wasm/qwasmlocalfileaccess.cpp b/src/gui/platform/wasm/qwasmlocalfileaccess.cpp
index 172c8f6814..a946cda043 100644
--- a/src/gui/platform/wasm/qwasmlocalfileaccess.cpp
+++ b/src/gui/platform/wasm/qwasmlocalfileaccess.cpp
@@ -2,19 +2,110 @@
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#include "qwasmlocalfileaccess_p.h"
+#include "qlocalfileapi_p.h"
#include <private/qstdweb_p.h>
#include <emscripten.h>
#include <emscripten/bind.h>
#include <emscripten/html5.h>
#include <emscripten/val.h>
+#include <QtCore/qregularexpression.h>
+
QT_BEGIN_NAMESPACE
namespace QWasmLocalFileAccess {
+namespace FileDialog {
+namespace {
+bool hasLocalFilesApi()
+{
+ return !qstdweb::window()["showOpenFilePicker"].isUndefined();
+}
+
+void showOpenViaHTMLPolyfill(const QStringList &accept, FileSelectMode fileSelectMode,
+ qstdweb::PromiseCallbacks onFilesSelected)
+{
+ // Create file input html element which will display a native file dialog
+ // and call back to our onchange handler once the user has selected
+ // one or more files.
+ emscripten::val document = emscripten::val::global("document");
+ emscripten::val input = document.call<emscripten::val>("createElement", std::string("input"));
+ input.set("type", "file");
+ input.set("style", "display:none");
+ input.set("accept", LocalFileApi::makeFileInputAccept(accept));
+ Q_UNUSED(accept);
+ input.set("multiple", emscripten::val(fileSelectMode == FileSelectMode::MultipleFiles));
+
+ // Note: there is no event in case the user cancels the file dialog.
+ static std::unique_ptr<qstdweb::EventCallback> changeEvent;
+ auto callback = [=](emscripten::val) { onFilesSelected.thenFunc(input["files"]); };
+ changeEvent = std::make_unique<qstdweb::EventCallback>(input, "change", callback);
+
+ // Activate file input
+ emscripten::val body = document["body"];
+ body.call<void>("appendChild", input);
+ input.call<void>("click");
+ body.call<void>("removeChild", input);
+}
+
+void showOpenViaLocalFileApi(const QStringList &accept, FileSelectMode fileSelectMode,
+ qstdweb::PromiseCallbacks callbacks)
+{
+ using namespace qstdweb;
+
+ auto options = LocalFileApi::makeOpenFileOptions(accept, fileSelectMode == FileSelectMode::MultipleFiles);
+
+ Promise::make(
+ window(), QStringLiteral("showOpenFilePicker"),
+ {
+ .thenFunc = [=](emscripten::val fileHandles) mutable {
+ std::vector<emscripten::val> filePromises;
+ filePromises.reserve(fileHandles["length"].as<int>());
+ for (int i = 0; i < fileHandles["length"].as<int>(); ++i)
+ filePromises.push_back(fileHandles[i].call<emscripten::val>("getFile"));
+ Promise::all(std::move(filePromises), callbacks);
+ },
+ .catchFunc = callbacks.catchFunc,
+ .finallyFunc = callbacks.finallyFunc,
+ }, std::move(options));
+}
+void showSaveViaLocalFileApi(const std::string &fileNameHint, qstdweb::PromiseCallbacks callbacks)
+{
+ using namespace qstdweb;
+ using namespace emscripten;
+
+ auto options = LocalFileApi::makeSaveFileOptions(QStringList(), fileNameHint);
+
+ Promise::make(
+ window(), QStringLiteral("showSaveFilePicker"),
+ std::move(callbacks), std::move(options));
+}
+} // namespace
+
+void showOpen(const QStringList &accept, FileSelectMode fileSelectMode,
+ qstdweb::PromiseCallbacks callbacks)
+{
+ hasLocalFilesApi() ?
+ showOpenViaLocalFileApi(accept, fileSelectMode, std::move(callbacks)) :
+ showOpenViaHTMLPolyfill(accept, fileSelectMode, std::move(callbacks));
+}
+
+bool canShowSave()
+{
+ return hasLocalFilesApi();
+}
+
+void showSave(const std::string &fileNameHint, qstdweb::PromiseCallbacks callbacks)
+{
+ Q_ASSERT(canShowSave());
+ showSaveViaLocalFileApi(fileNameHint, std::move(callbacks));
+}
+} // namespace FileDialog
+
+namespace {
void readFiles(const qstdweb::FileList &fileList,
- const std::function<char *(uint64_t size, const std::string name)> &acceptFile,
- const std::function<void ()> &fileDataReady)
+ const std::function<char *(uint64_t size, const std::string name)> &acceptFile,
+ const std::function<void ()> &fileDataReady)
{
auto readFile = std::make_shared<std::function<void(int)>>();
@@ -25,7 +116,7 @@ void readFiles(const qstdweb::FileList &fileList,
return;
}
- const qstdweb::File file = fileList[fileIndex];
+ const qstdweb::File file = qstdweb::File(fileList[fileIndex]);
// Ask caller if the file should be accepted
char *buffer = acceptFile(file.size(), file.name());
@@ -35,7 +126,7 @@ void readFiles(const qstdweb::FileList &fileList,
}
// Read file data into caller-provided buffer
- file.stream(buffer, [=]() {
+ file.stream(buffer, [readFile = readFile.get(), fileIndex, fileDataReady]() {
fileDataReady();
(*readFile)(fileIndex + 1);
});
@@ -44,53 +135,21 @@ void readFiles(const qstdweb::FileList &fileList,
(*readFile)(0);
}
-typedef std::function<void (const qstdweb::FileList &fileList)> OpenFileDialogCallback;
-void openFileDialog(const std::string &accept, FileSelectMode fileSelectMode,
- const OpenFileDialogCallback &filesSelected)
+QStringList makeFilterList(const std::string &qtAcceptList)
{
- // Create file input html element which will display a native file dialog
- // and call back to our onchange handler once the user has selected
- // one or more files.
- emscripten::val document = emscripten::val::global("document");
- emscripten::val input = document.call<emscripten::val>("createElement", std::string("input"));
- input.set("type", "file");
- input.set("style", "display:none");
- input.set("accept", emscripten::val(accept));
- input.set("multiple", emscripten::val(fileSelectMode == MultipleFiles));
-
- // Note: there is no event in case the user cancels the file dialog.
- static std::unique_ptr<qstdweb::EventCallback> changeEvent;
- auto callback = [=](emscripten::val) { filesSelected(qstdweb::FileList(input["files"])); };
- changeEvent.reset(new qstdweb::EventCallback(input, "change", callback));
-
- // Activate file input
- emscripten::val body = document["body"];
- body.call<void>("appendChild", input);
- input.call<void>("click");
- body.call<void>("removeChild", input);
-}
+ // copy of qt_make_filter_list() from qfiledialog.cpp
+ auto filter = QString::fromStdString(qtAcceptList);
+ if (filter.isEmpty())
+ return QStringList();
+ QString sep(";;");
+ if (!filter.contains(sep) && filter.contains(u'\n'))
+ sep = u'\n';
-void openFiles(const std::string &accept, FileSelectMode fileSelectMode,
- const std::function<void (int fileCount)> &fileDialogClosed,
- const std::function<char *(uint64_t size, const std::string name)> &acceptFile,
- const std::function<void()> &fileDataReady)
-{
- openFileDialog(accept, fileSelectMode, [=](const qstdweb::FileList &files) {
- fileDialogClosed(files.length());
- readFiles(files, acceptFile, fileDataReady);
- });
+ return filter.split(sep);
}
-
-void openFile(const std::string &accept,
- const std::function<void (bool fileSelected)> &fileDialogClosed,
- const std::function<char *(uint64_t size, const std::string name)> &acceptFile,
- const std::function<void()> &fileDataReady)
-{
- auto fileDialogClosedWithInt = [=](int fileCount) { fileDialogClosed(fileCount != 0); };
- openFiles(accept, FileSelectMode::SingleFile, fileDialogClosedWithInt, acceptFile, fileDataReady);
}
-void saveFile(const char *content, size_t size, const std::string &fileNameHint)
+void downloadDataAsFile(const char *content, size_t size, const std::string &fileNameHint)
{
// Save a file by creating programmatically clicking a download
// link to an object url to a Blob containing a copy of the file
@@ -98,7 +157,7 @@ void saveFile(const char *content, size_t size, const std::string &fileNameHint)
// can be released as soon as this function returns.
qstdweb::Blob contentBlob = qstdweb::Blob::copyFrom(content, size);
emscripten::val document = emscripten::val::global("document");
- emscripten::val window = emscripten::val::global("window");
+ emscripten::val window = qstdweb::window();
emscripten::val contentUrl = window["URL"].call<emscripten::val>("createObjectURL", contentBlob.val());
emscripten::val contentLink = document.call<emscripten::val>("createElement", std::string("a"));
contentLink.set("href", contentUrl);
@@ -113,6 +172,118 @@ void saveFile(const char *content, size_t size, const std::string &fileNameHint)
window["URL"].call<emscripten::val>("revokeObjectURL", contentUrl);
}
+void openFiles(const std::string &accept, FileSelectMode fileSelectMode,
+ const std::function<void (int fileCount)> &fileDialogClosed,
+ const std::function<char *(uint64_t size, const std::string& name)> &acceptFile,
+ const std::function<void()> &fileDataReady)
+{
+ FileDialog::showOpen(makeFilterList(accept), fileSelectMode, {
+ .thenFunc = [=](emscripten::val result) {
+ auto files = qstdweb::FileList(result);
+ fileDialogClosed(files.length());
+ readFiles(files, acceptFile, fileDataReady);
+ },
+ .catchFunc = [=](emscripten::val) {
+ fileDialogClosed(0);
+ }
+ });
+}
+
+void openFile(const std::string &accept,
+ const std::function<void (bool fileSelected)> &fileDialogClosed,
+ const std::function<char *(uint64_t size, const std::string& name)> &acceptFile,
+ const std::function<void()> &fileDataReady)
+{
+ auto fileDialogClosedWithInt = [=](int fileCount) { fileDialogClosed(fileCount != 0); };
+ openFiles(accept, FileSelectMode::SingleFile, fileDialogClosedWithInt, acceptFile, fileDataReady);
+}
+
+void saveDataToFileInChunks(emscripten::val fileHandle, const QByteArray &data)
+{
+ using namespace emscripten;
+ using namespace qstdweb;
+
+ Promise::make(fileHandle, QStringLiteral("createWritable"), {
+ .thenFunc = [=](val writable) {
+ struct State {
+ size_t written;
+ std::function<void(val result)> continuation;
+ };
+
+ static constexpr size_t desiredChunkSize = 1024u;
+#if defined(__EMSCRIPTEN_SHARED_MEMORY__)
+ qstdweb::Uint8Array chunkArray(desiredChunkSize);
+#endif
+
+ auto state = std::make_shared<State>();
+ state->written = 0u;
+ state->continuation = [=](val) mutable {
+ const size_t remaining = data.size() - state->written;
+ if (remaining == 0) {
+ Promise::make(writable, QStringLiteral("close"), { .thenFunc = [=](val) {} });
+ state.reset();
+ return;
+ }
+
+ const auto currentChunkSize = std::min(remaining, desiredChunkSize);
+
+#if defined(__EMSCRIPTEN_SHARED_MEMORY__)
+ // If shared memory is used, WebAssembly.Memory is instantiated with the 'shared'
+ // option on. Passing a typed_memory_view to SharedArrayBuffer to
+ // FileSystemWritableFileStream.write is disallowed by security policies, so we
+ // need to make a copy of the data to a chunk array buffer.
+ Promise::make(
+ writable, QStringLiteral("write"),
+ {
+ .thenFunc = state->continuation,
+ },
+ chunkArray.copyFrom(data.constData() + state->written, currentChunkSize)
+ .val()
+ .call<emscripten::val>("subarray", emscripten::val(0),
+ emscripten::val(currentChunkSize)));
+#else
+ Promise::make(writable, QStringLiteral("write"),
+ {
+ .thenFunc = state->continuation,
+ },
+ val(typed_memory_view(currentChunkSize, data.constData() + state->written)));
+#endif
+ state->written += currentChunkSize;
+ };
+
+ state->continuation(val::undefined());
+ },
+ });
+}
+
+void saveFile(const QByteArray &data, const std::string &fileNameHint)
+{
+ if (!FileDialog::canShowSave()) {
+ downloadDataAsFile(data.constData(), data.size(), fileNameHint);
+ return;
+ }
+
+ FileDialog::showSave(fileNameHint, {
+ .thenFunc = [=](emscripten::val result) {
+ saveDataToFileInChunks(result, data);
+ },
+ });
+}
+
+void saveFile(const char *content, size_t size, const std::string &fileNameHint)
+{
+ if (!FileDialog::canShowSave()) {
+ downloadDataAsFile(content, size, fileNameHint);
+ return;
+ }
+
+ FileDialog::showSave(fileNameHint, {
+ .thenFunc = [=](emscripten::val result) {
+ saveDataToFileInChunks(result, QByteArray(content, size));
+ },
+ });
+}
+
} // namespace QWasmLocalFileAccess
QT_END_NAMESPACE
diff --git a/src/gui/platform/wasm/qwasmlocalfileaccess_p.h b/src/gui/platform/wasm/qwasmlocalfileaccess_p.h
index eb73463759..77b14577f7 100644
--- a/src/gui/platform/wasm/qwasmlocalfileaccess_p.h
+++ b/src/gui/platform/wasm/qwasmlocalfileaccess_p.h
@@ -23,18 +23,19 @@ QT_BEGIN_NAMESPACE
namespace QWasmLocalFileAccess {
-enum FileSelectMode { SingleFile, MultipleFiles };
+enum class FileSelectMode { SingleFile, MultipleFiles };
Q_CORE_EXPORT void openFiles(const std::string &accept, FileSelectMode fileSelectMode,
const std::function<void (int fileCount)> &fileDialogClosed,
- const std::function<char *(uint64_t size, const std::string name)> &acceptFile,
+ const std::function<char *(uint64_t size, const std::string& name)> &acceptFile,
const std::function<void()> &fileDataReady);
Q_CORE_EXPORT void openFile(const std::string &accept,
const std::function<void (bool fileSelected)> &fileDialogClosed,
- const std::function<char *(uint64_t size, const std::string name)> &acceptFile,
+ const std::function<char *(uint64_t size, const std::string& name)> &acceptFile,
const std::function<void()> &fileDataReady);
+Q_CORE_EXPORT void saveFile(const QByteArray &data, const std::string &fileNameHint);
Q_CORE_EXPORT void saveFile(const char *content, size_t size, const std::string &fileNameHint);
} // namespace QWasmLocalFileAccess
diff --git a/src/gui/platform/wasm/qwasmnativeinterface.cpp b/src/gui/platform/wasm/qwasmnativeinterface.cpp
new file mode 100644
index 0000000000..7313629a8d
--- /dev/null
+++ b/src/gui/platform/wasm/qwasmnativeinterface.cpp
@@ -0,0 +1,17 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+
+
+#include <QtGui/private/qguiapplication_p.h>
+#include <qpa/qplatformintegration.h>
+#include <qpa/qplatformwindow_p.h>
+
+
+QT_BEGIN_NAMESPACE
+
+using namespace QNativeInterface::Private;
+
+QT_DEFINE_PRIVATE_NATIVE_INTERFACE(QWasmWindow);
+
+QT_END_NAMESPACE
diff --git a/src/gui/platform/windows/qwindowsguieventdispatcher.cpp b/src/gui/platform/windows/qwindowsguieventdispatcher.cpp
index f70655380d..c2f0efe96e 100644
--- a/src/gui/platform/windows/qwindowsguieventdispatcher.cpp
+++ b/src/gui/platform/windows/qwindowsguieventdispatcher.cpp
@@ -197,3 +197,5 @@ const char *QWindowsGuiEventDispatcher::windowsMessageName(UINT msg)
}
QT_END_NAMESPACE
+
+#include "moc_qwindowsguieventdispatcher_p.cpp"
diff --git a/src/gui/platform/windows/qwindowsmimeconverter.cpp b/src/gui/platform/windows/qwindowsmimeconverter.cpp
new file mode 100644
index 0000000000..49d524cb99
--- /dev/null
+++ b/src/gui/platform/windows/qwindowsmimeconverter.cpp
@@ -0,0 +1,170 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include "qwindowsmimeconverter.h"
+
+#include <QtCore/qt_windows.h>
+
+#include <QtGui/private/qguiapplication_p.h>
+#include <QtGui/qpa/qplatformintegration.h>
+
+QT_BEGIN_NAMESPACE
+
+/*!
+ \class QWindowsMimeConverter
+ \brief The QWindowsMimeConverter class maps open-standard MIME to Window Clipboard formats.
+ \inmodule QtGui
+
+ Qt's drag-and-drop and clipboard facilities use the MIME standard.
+ On X11, this maps trivially to the Xdnd protocol, but on Windows
+ although some applications use MIME types to describe clipboard
+ formats, others use arbitrary non-standardized naming conventions,
+ or unnamed built-in formats of Windows.
+
+ By instantiating subclasses of QWindowsMimeConverter that provide
+ conversions between Windows Clipboard and MIME formats, you can convert
+ proprietary clipboard formats to MIME formats.
+
+ Construct an instance of your converter implementation after instantiating
+ QGuiApplication:
+
+ \code
+ int main(int argc, char **argv)
+ {
+ QGuiApplication app(argc, argv);
+ JsonMimeConverter jsonConverter;
+ }
+ \endcode
+
+ Destroying the instance will unregister the converter and remove support
+ for the conversion. It is also valid to heap-allocate the converter
+ instance; Qt takes ownership and will delete the converter object during
+ QGuiApplication shut-down.
+
+ Qt has predefined support for the following Windows Clipboard formats:
+
+ \table
+ \header \li Windows Format \li Equivalent MIME type
+ \row \li \c CF_UNICODETEXT \li \c text/plain
+ \row \li \c CF_TEXT \li \c text/plain
+ \row \li \c CF_DIB \li \c{image/xyz}, where \c xyz is
+ a \l{QImageWriter::supportedImageFormats()}{Qt image format}
+ \row \li \c CF_HDROP \li \c text/uri-list
+ \row \li \c CF_INETURL \li \c text/uri-list
+ \row \li \c CF_HTML \li \c text/html
+ \endtable
+
+ An example use of this class would be to map the Windows Metafile
+ clipboard format (\c CF_METAFILEPICT) to and from the MIME type
+ \c{image/x-wmf}. This conversion might simply be adding or removing
+ a header, or even just passing on the data. See \l{Drag and Drop}
+ for more information on choosing and definition MIME types.
+
+ You can check if a MIME type is convertible using canConvertFromMime() and
+ can perform conversions with convertToMime() and convertFromMime().
+*/
+
+
+/*!
+ \fn bool QWindowsMimeConverter::canConvertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData) const
+
+ Returns \c true if the converter can convert from the \a mimeData to
+ the format specified in \a formatetc.
+
+ All subclasses must reimplement this pure virtual function.
+*/
+
+/*!
+ \fn bool QWindowsMimeConverter::canConvertToMime(const QString &mimeType, IDataObject *pDataObj) const
+
+ Returns \c true if the converter can convert to the \a mimeType from
+ the available formats in \a pDataObj.
+
+ All subclasses must reimplement this pure virtual function.
+*/
+
+/*!
+ \fn QString QWindowsMimeConverter::mimeForFormat(const FORMATETC &formatetc) const
+
+ Returns the mime type that will be created form the format specified
+ in \a formatetc, or an empty string if this converter does not support
+ \a formatetc.
+
+ All subclasses must reimplement this pure virtual function.
+*/
+
+/*!
+ \fn QList<FORMATETC> QWindowsMimeConverter::formatsForMime(const QString &mimeType, const QMimeData *mimeData) const
+
+ Returns a QList of FORMATETC structures representing the different windows clipboard
+ formats that can be provided for the \a mimeType from the \a mimeData.
+
+ All subclasses must reimplement this pure virtual function.
+*/
+
+/*!
+ \fn QVariant QWindowsMimeConverter::convertToMime(const QString &mimeType, IDataObject *pDataObj,
+ QMetaType preferredType) const
+
+ Returns a QVariant containing the converted data for \a mimeType from \a pDataObj.
+ If possible the QVariant should be of the \a preferredType to avoid needless conversions.
+
+ All subclasses must reimplement this pure virtual function.
+*/
+
+/*!
+ \fn bool QWindowsMimeConverter::convertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData, STGMEDIUM * pmedium) const
+
+ Convert the \a mimeData to the format specified in \a formatetc.
+ The converted data should then be placed in \a pmedium structure.
+
+ Return true if the conversion was successful.
+
+ All subclasses must reimplement this pure virtual function.
+*/
+
+/*!
+ Constructs a QWindowsMimeConverter instance.
+
+ The instance is automatically registered, and will be called to convert data during
+ clipboard or drag'n'drop operations.
+
+ Call this constructor after QGuiApplication has been created.
+*/
+QWindowsMimeConverter::QWindowsMimeConverter()
+{
+ using QWindowsApplication = QNativeInterface::Private::QWindowsApplication;
+ auto nativeWindowsApp = dynamic_cast<QWindowsApplication *>(QGuiApplicationPrivate::platformIntegration());
+ Q_ASSERT(nativeWindowsApp);
+ nativeWindowsApp->registerMime(this);
+}
+
+/*!
+ Constructs a QWindowsMimeConverter instance.
+
+ The instance is automatically unregistered.
+*/
+QWindowsMimeConverter::~QWindowsMimeConverter()
+{
+ using QWindowsApplication = QNativeInterface::Private::QWindowsApplication;
+ auto nativeWindowsApp = dynamic_cast<QWindowsApplication *>(QGuiApplicationPrivate::platformIntegration());
+ Q_ASSERT(nativeWindowsApp);
+ nativeWindowsApp->unregisterMime(this);
+}
+
+/*!
+ Registers the MIME type \a mimeType, and returns an ID number
+ identifying the format on Windows.
+
+ A mime type \c {application/x-qt-windows-mime;value="WindowsType"} will be
+ registered as the clipboard format for \c WindowsType.
+*/
+int QWindowsMimeConverter::registerMimeType(const QString &mimeType)
+{
+ using QWindowsApplication = QNativeInterface::Private::QWindowsApplication;
+ auto nativeWindowsApp = dynamic_cast<QWindowsApplication *>(QGuiApplicationPrivate::platformIntegration());
+ Q_ASSERT(nativeWindowsApp);
+ return nativeWindowsApp->registerMimeType(mimeType);
+}
+
+QT_END_NAMESPACE
diff --git a/src/gui/platform/windows/qwindowsmime_p.h b/src/gui/platform/windows/qwindowsmimeconverter.h
index a2fbd38119..145355fe15 100644
--- a/src/gui/platform/windows/qwindowsmime_p.h
+++ b/src/gui/platform/windows/qwindowsmimeconverter.h
@@ -1,36 +1,31 @@
-// Copyright (C) 2020 The Qt Company Ltd.
+// Copyright (C) 2022 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
-#ifndef QWINDOWSMIME_P_H
-#define QWINDOWSMIME_P_H
-
-//
-// W A R N I N G
-// -------------
-//
-// This file is not part of the Qt API. It exists purely as an
-// implementation detail. This header file may change from version to
-// version without notice, or even be removed.
-//
-// We mean it.
-//
-
-#include <QtCore/qt_windows.h>
-#include <QtCore/qvariant.h>
+#ifndef QWINDOWSMIMECONVERTER_P_H
+#define QWINDOWSMIMECONVERTER_P_H
#include <QtGui/qtguiglobal.h>
-#include <QtCore/private/qglobal_p.h>
+
+struct tagFORMATETC;
+using FORMATETC = tagFORMATETC;
+struct tagSTGMEDIUM;
+using STGMEDIUM = tagSTGMEDIUM;
+struct IDataObject;
QT_BEGIN_NAMESPACE
+class QMetaType;
class QMimeData;
+class QVariant;
-namespace QNativeInterface::Private {
-
-class Q_GUI_EXPORT QWindowsMime
+class Q_GUI_EXPORT QWindowsMimeConverter
{
+ Q_DISABLE_COPY(QWindowsMimeConverter)
public:
- virtual ~QWindowsMime() = default;
+ QWindowsMimeConverter();
+ virtual ~QWindowsMimeConverter();
+
+ static int registerMimeType(const QString &mimeType);
// for converting from Qt
virtual bool canConvertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData) const = 0;
@@ -43,8 +38,6 @@ public:
virtual QString mimeForFormat(const FORMATETC &formatetc) const = 0;
};
-} // QNativeInterface::Private
-
QT_END_NAMESPACE
-#endif // QWINDOWSMIME_P_H
+#endif // QWINDOWSMIMECONVERTER_H
diff --git a/src/gui/platform/windows/qwindowsnativeinterface.cpp b/src/gui/platform/windows/qwindowsnativeinterface.cpp
index fe6f4e96dd..44f230e1d3 100644
--- a/src/gui/platform/windows/qwindowsnativeinterface.cpp
+++ b/src/gui/platform/windows/qwindowsnativeinterface.cpp
@@ -3,7 +3,6 @@
#include <QtGui/qopenglcontext.h>
#include <QtGui/private/qguiapplication_p.h>
-#include <QtGui/private/qwindowsmime_p.h>
#include <qpa/qplatformopenglcontext.h>
#include <qpa/qplatformintegration.h>
#include <qpa/qplatformwindow.h>
@@ -90,14 +89,20 @@ QOpenGLContext *QNativeInterface::QWGLContext::fromNative(HGLRC context, HWND wi
QT_DEFINE_PRIVATE_NATIVE_INTERFACE(QWindowsApplication);
/*!
- \class QNativeInterface::Private::QWindowsScreen
- \since 6.5
- \internal
- \brief Native interface to QScreen, to be retrieved from QPlatformIntegration.
+ \class QNativeInterface::QWindowsScreen
+ \since 6.7
+ \brief Native interface to a screen.
+
+ Accessed through QScreen::nativeInterface().
\inmodule QtGui
\ingroup native-interfaces
+ \ingroup native-interfaces-qscreen
*/
-QT_DEFINE_PRIVATE_NATIVE_INTERFACE(QWindowsScreen);
+/*!
+ * \fn HWMONITOR QNativeInterface::QWindowsScreen::handle() const;
+ * \return The underlying HWMONITOR of the screen.
+ */
+QT_DEFINE_NATIVE_INTERFACE(QWindowsScreen);
/*!
\enum QNativeInterface::Private::QWindowsApplication::TouchWindowTouchType
@@ -176,15 +181,7 @@ QT_DEFINE_PRIVATE_NATIVE_INTERFACE(QWindowsScreen);
\value DarkModeStyle The Windows Vista style will be turned off and
a simple dark style will be used.
- \sa isDarkMode(), setDarkModeHandling()
-*/
-
-/*!
- \fn bool QNativeInterface::Private::QWindowsApplication::isDarkMode() const = 0
- \internal
-
- Returns \c true if Windows 10 is configured to use dark mode for
- applications.
+ \sa setDarkModeHandling()
*/
/*!
@@ -218,21 +215,21 @@ QT_DEFINE_PRIVATE_NATIVE_INTERFACE(QWindowsScreen);
*/
/*!
- \fn bool QNativeInterface::Private::QWindowsApplication::registerMime(QWindowsMime *mime)
+ \fn bool QNativeInterface::Private::QWindowsApplication::registerMime(QWindowsMimeConverter *mime)
\internal
Registers the converter \a mime to the system.
- \sa QNativeInterface::Private::QWindowsMime, unregisterMime()
+ \sa QWindowsMimeConverter, unregisterMime()
*/
/*!
- \fn void QNativeInterface::Private::QWindowsApplication::unregisterMime(QWindowsMime *mime)
+ \fn void QNativeInterface::Private::QWindowsApplication::unregisterMime(QWindowsMimeConverter *mime)
\internal
Unregisters the converter \a mime from the system.
- \sa QNativeInterface::Private::QWindowsMime, registerMime()
+ \sa QWindowsMimeConverter, registerMime()
*/
/*!
diff --git a/src/gui/platform/windows/qwindowsthemecache.cpp b/src/gui/platform/windows/qwindowsthemecache.cpp
new file mode 100644
index 0000000000..3bb92e67ca
--- /dev/null
+++ b/src/gui/platform/windows/qwindowsthemecache.cpp
@@ -0,0 +1,79 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include "qwindowsthemecache_p.h"
+#include <QtCore/qdebug.h>
+#include <QtCore/qhash.h>
+
+QT_BEGIN_NAMESPACE
+
+// Theme names matching the QWindowsVistaStylePrivate::Theme enumeration.
+constexpr const wchar_t *themeNames[] = {
+ L"BUTTON", L"COMBOBOX", L"EDIT", L"HEADER", L"LISTVIEW",
+ L"MENU", L"PROGRESS", L"REBAR", L"SCROLLBAR", L"SPIN",
+ L"TAB", L"TASKDIALOG", L"TOOLBAR", L"TOOLTIP", L"TRACKBAR",
+ L"WINDOW", L"STATUS", L"TREEVIEW"
+};
+
+typedef std::array<HTHEME, std::size(themeNames)> ThemeArray;
+typedef QHash<HWND, ThemeArray> ThemesCache;
+Q_GLOBAL_STATIC(ThemesCache, themesCache);
+
+QString QWindowsThemeCache::themeName(int theme)
+{
+ return theme >= 0 && theme < int(std::size(themeNames))
+ ? QString::fromWCharArray(themeNames[theme]) : QString();
+}
+
+HTHEME QWindowsThemeCache::createTheme(int theme, HWND hwnd)
+{
+ if (Q_UNLIKELY(theme < 0 || theme >= int(std::size(themeNames)) || !hwnd)) {
+ qWarning("Invalid parameters #%d, %p", theme, hwnd);
+ return nullptr;
+ }
+
+ // Get or create themes array for this window.
+ ThemesCache *cache = themesCache();
+ auto it = cache->find(hwnd);
+ if (it == cache->end())
+ it = cache->insert(hwnd, ThemeArray {});
+
+ // Get or create theme data
+ ThemeArray &themes = *it;
+ if (!themes[theme]) {
+ const wchar_t *name = themeNames[theme];
+ themes[theme] = OpenThemeData(hwnd, name);
+ if (Q_UNLIKELY(!themes[theme]))
+ qErrnoWarning("OpenThemeData() failed for theme %d (%s).",
+ theme, qPrintable(themeName(theme)));
+ }
+ return themes[theme];
+}
+
+static void clearThemes(ThemeArray &themes)
+{
+ for (auto &theme : themes) {
+ if (theme) {
+ CloseThemeData(theme);
+ theme = nullptr;
+ }
+ }
+}
+
+void QWindowsThemeCache::clearThemeCache(HWND hwnd)
+{
+ ThemesCache *cache = themesCache();
+ auto it = cache->find(hwnd);
+ if (it == cache->end())
+ return;
+ clearThemes(*it);
+}
+
+void QWindowsThemeCache::clearAllThemeCaches()
+{
+ ThemesCache *cache = themesCache();
+ for (auto &themeArray : *cache)
+ clearThemes(themeArray);
+}
+
+QT_END_NAMESPACE
diff --git a/src/gui/platform/windows/qwindowsthemecache_p.h b/src/gui/platform/windows/qwindowsthemecache_p.h
new file mode 100644
index 0000000000..beb724dc5c
--- /dev/null
+++ b/src/gui/platform/windows/qwindowsthemecache_p.h
@@ -0,0 +1,35 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef QWINDOWSTHEME_CACHE_P_H
+#define QWINDOWSTHEME_CACHE_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include "QtGui/private/qtguiglobal_p.h"
+
+#include <QtCore/qt_windows.h>
+#include <uxtheme.h>
+
+QT_BEGIN_NAMESPACE
+
+namespace QWindowsThemeCache
+{
+ Q_GUI_EXPORT QString themeName(int theme);
+ Q_GUI_EXPORT HTHEME createTheme(int theme, HWND hwnd);
+ Q_GUI_EXPORT void clearThemeCache(HWND hwnd);
+ Q_GUI_EXPORT void clearAllThemeCaches();
+}
+
+QT_END_NAMESPACE
+
+#endif // QWINDOWSTHEME_CACHE_P_H