summaryrefslogtreecommitdiff
path: root/x11-misc/copyq
diff options
context:
space:
mode:
Diffstat (limited to 'x11-misc/copyq')
-rw-r--r--x11-misc/copyq/Manifest10
-rw-r--r--x11-misc/copyq/copyq-6.4.0.ebuild120
-rw-r--r--x11-misc/copyq/copyq-7.1.0.ebuild138
-rw-r--r--x11-misc/copyq/files/copyq-7.1.0-fix-gpg-2.1-support.patch558
-rw-r--r--x11-misc/copyq/files/copyq-7.1.0-fix-qt-6.6.0-build.patch44
-rw-r--r--x11-misc/copyq/files/copyq-7.1.0-fix-test-failure-due-to-invalid-regex.patch98
-rw-r--r--x11-misc/copyq/files/copyq-7.1.0-support-plugin-dir-envvar.patch26
-rw-r--r--x11-misc/copyq/metadata.xml4
8 files changed, 875 insertions, 123 deletions
diff --git a/x11-misc/copyq/Manifest b/x11-misc/copyq/Manifest
index ba148c07f1c2..26899da85b66 100644
--- a/x11-misc/copyq/Manifest
+++ b/x11-misc/copyq/Manifest
@@ -1,5 +1,9 @@
-DIST copyq-6.4.0.tar.gz 3316278 BLAKE2B 348fdc23a6d0d53ddcc8e2c32b194cfbf6c4d4d2374b972cb81d945e284c42d1e8f6b9ed30e657e43e69ed0f35661adc7875392b5daf653ce895d76afed7c09c SHA512 a97b4ac541ff73129a6283266fb8857d89d571d042829de5793b94e6423a2978f632b22728ca663bccd540bb90fed51c755b432d1d2545f75c227ea2cb0d9581
+AUX copyq-7.1.0-fix-gpg-2.1-support.patch 20025 BLAKE2B faab876bfd8813afea0ed2f9e822e4604cf1813233af86bd9a49ee54ff0fc121333b9a7e8aa549c9a39751cd9cb4f12b73c7b8bfd714f00715a93a8acc7a553f SHA512 d1022e3141273d1bcd1bf85822e1113cc03503c740e27f38453d1e29c8d5524e8f9dc3be4b41b82db9e5cdb845f66dcf234aac207556ca80275c63e1bda87d66
+AUX copyq-7.1.0-fix-qt-6.6.0-build.patch 1671 BLAKE2B f5e563ff590ee05ae09f37887e2ce6e72000ac13f37ccfcff7a7f9cfece2d83160fa2e8c462087f2eed377601f87bb5bc8469d2ef6734e375e662838b889356c SHA512 773b31a7976358be31a09aed93d2eca12768f8c3a8541b822e833cef409f39eb26819db2fd49ce7e2eec7b8419e0ed60bbe92c25f69de2f15a9a15fd6fa812aa
+AUX copyq-7.1.0-fix-test-failure-due-to-invalid-regex.patch 3822 BLAKE2B f95dfe294136907ee0f22d8f9810989928da18642ab674da9725daa029d75adebe4b8c5fd9d71a92298bd46b464ed9b20a6a12c2689f6f96c09742c1fda5d96b SHA512 415a24815233668e51303b3188bde028f0cfcb9483f92ec143a4350d2130f2b4752f704aa5ee27997be1ce44c3dabf2a384a7fb97756367a6be1029a10e04ec0
+AUX copyq-7.1.0-support-plugin-dir-envvar.patch 766 BLAKE2B 6e837fe85d2279b67027ccfd37558cd88fe1ecb8b160c97030d5eadc83b53aebe6ddbdee10da75672fd1ee61cfc7b4641180ec6838dd23e659d432a91e54617d SHA512 b03fa69c770c8517162e0e5462248088c8f4cd8017de53ae0d97a12e2c395b6e11649fb34ba96fea8a536d003921e3cfc463fd878c2c2b71ca72faf3d4d1c67d
DIST copyq-7.0.0.tar.gz 3323354 BLAKE2B 3c71bf94ed97d0564f89cb0b9927024df21520cf9eb758ec8c40e8156d9796b3c6df5518b9ad223c12489fe7aca3a067f772719a3a757f9a92e9ec18fe79e38c SHA512 f0b84ddef6791e229c625dbdefab2d3aad5be10d68745addb64d6e2b1546e033f1f95fb1a1218f9fdd19b5fcdddf2d840b2480ad54e0f59a7d5741781b3a43c4
-EBUILD copyq-6.4.0.ebuild 2248 BLAKE2B 4922496354c0bcf3437cc14725d59f1014918d34884d240b0f354e26c171874dfda6b90abf92dfc5e2e8867c66ff3925999b310cf3dd4cc4046f28781cd98c4f SHA512 99d49a578a214f05214c151a4583810fae1eca4eda30283a83e3e5a3926d68d424195ce9af2ec7bf0bee7b4327a37548b2933f21d11ab53a507246ad0c4e5c56
+DIST copyq-7.1.0.tar.gz 3351093 BLAKE2B 758271f6bb54760372b8b5ab84de7c91af874bd72a22c8c22d338705869eab5500fde90808b4bf1288f8bdbad11163283637b81d85c09ccf0d734286dee605b6 SHA512 4320095ab75c361cc3d553c7817951eb6e74d47223f62bf6c1722e0f0b0d3ff59a1762354cf46fe0de064d516d60a467bff9ad9143b12016fc3f9e62139d3909
EBUILD copyq-7.0.0.ebuild 1942 BLAKE2B 04362e75f351d9ffc2477588fbe7ffa89f58d5461b3b9784b0c5c541c21f0f11687c64f7df8ffa4b5e2f9e9a696e4a8e1bbe49e5c0ba87dd8eedd1bbbe2fdb63 SHA512 7b669810c75a9317941690ab9a42462de2e4db7c7db204e0f1cef2a69fe3f5f384267e7cbb8327cdc1214feaecc98c0454da12e17bedc262cfb488e0889868ea
-MISC metadata.xml 243 BLAKE2B c814627bd719a7e626871bf03cb2a0d2a523bff91989d9e77ef27f5c234095eaf737edf0a9e795fbe5f2317884d6d8e735e19742776ef023b37c1364be1387c7 SHA512 802294fe73a224455ea8ea3ad5ed320c3f8cc803ba90cf6805a9a6bfafb627dee7334563f901d5c01ac37d2134d88aba1075defd1ca7420022a4cee996af778d
+EBUILD copyq-7.1.0.ebuild 3627 BLAKE2B 5f20de414218f4cd2cc5673528c86b319255c04e626b9ec48d74aa4c526d40520fe24baddd4dddf2a8463ff6c32ae7f4a2898d16a2ca86c03c24d2788c82bc4e SHA512 38ff1ab91ac10b9377f60940ef9fc51aec87a028d310861f49e4901099bdf3dc91473d800cba5f0d75b2564c6529e421339c69691fe8831ecb2b52a77e85d0ee
+MISC metadata.xml 429 BLAKE2B 22f09cc6c63f32d0d0bcc5dc2cf7fb8d437653e0358ba30aeb3ab79978cafd9ea971fb0f56931744146e5ab7a16401e5f366d31f91ffba52ca6c2c26c1ba5931 SHA512 32f0a4a4c0065463ae826511758b42f7a2c815a6d49657dec47560b9510f21d44fe0d6f397922998fd3ad022e9b4fa88c45babdd9aa3874d8db0cfdbab7813d8
diff --git a/x11-misc/copyq/copyq-6.4.0.ebuild b/x11-misc/copyq/copyq-6.4.0.ebuild
deleted file mode 100644
index 89972837ae26..000000000000
--- a/x11-misc/copyq/copyq-6.4.0.ebuild
+++ /dev/null
@@ -1,120 +0,0 @@
-# Copyright 1999-2023 Gentoo Authors
-# Distributed under the terms of the GNU General Public License v2
-
-EAPI=8
-
-inherit cmake edo optfeature virtualx xdg
-
-DESCRIPTION="Clipboard manager with advanced features"
-HOMEPAGE="https://github.com/hluk/CopyQ"
-SRC_URI="https://github.com/hluk/${PN}/archive/v${PV}.tar.gz -> ${P}.tar.gz"
-S="${WORKDIR}/CopyQ-${PV}"
-
-LICENSE="GPL-3+"
-SLOT="0"
-KEYWORDS="~amd64 ~x86 ~amd64-linux ~x86-linux"
-IUSE="debug kde qt6 test"
-
-# Qt6 tests fail with "Failed to stop server" error
-RESTRICT="
- qt6? ( test )
- !test? ( test )
-"
-
-RDEPEND="
- dev-libs/wayland
- x11-libs/libX11
- x11-libs/libXfixes
- x11-libs/libXtst
- !qt6? (
- dev-qt/qtcore:5
- dev-qt/qtdeclarative:5
- dev-qt/qtgui:5
- dev-qt/qtnetwork:5
- dev-qt/qtsvg:5
- dev-qt/qtwayland:5
- dev-qt/qtwidgets:5
- dev-qt/qtx11extras:5
- dev-qt/qtxml:5
- kde? ( kde-frameworks/knotifications:5 )
- test? ( dev-qt/qttest:5 )
- )
- qt6? (
- dev-qt/qtbase:6=[X,gui,network,widgets,xml(+)]
- dev-qt/qtdeclarative:6
- dev-qt/qtsvg:6
- dev-qt/qtwayland:6
- )
-"
-DEPEND="${RDEPEND}
- x11-base/xorg-proto
-"
-BDEPEND="
- kde-frameworks/extra-cmake-modules:0
- !qt6? (
- dev-qt/linguist-tools:5
- dev-qt/qtwaylandscanner:5
- )
- qt6? (
- dev-qt/qttools:6[linguist]
- dev-qt/qtwayland:6
- dev-util/wayland-scanner
- )
- test? (
- app-crypt/gnupg
- x11-wm/icewm
- )
-"
-
-src_configure() {
- if use debug; then
- # Add debug definitions
- CMAKE_BUILD_TYPE="Debug"
- fi
-
- local mycmakeargs=(
- -DPLUGIN_INSTALL_PREFIX="${EPREFIX}/usr/$(get_libdir)/${PN}/plugins"
- -DWITH_NATIVE_NOTIFICATIONS=$(usex kde)
- -DWITH_QT6=$(usex qt6)
- -DWITH_TESTS=$(usex test)
- )
-
- cmake_src_configure
-}
-
-my_src_test() {
- local -x COPYQ_TESTS_RERUN_FAILED=0
- local -x COPYQ_TESTS_NO_NETWORK=1
-
- local plug
- local plugins=(
- itemencrypted
- itemfakevim
- itempinned
- #itemsync -- failure in avoidDuplicateItemsAddedFromClipboard()
- itemtags
- )
-
- ebegin "Starting IceWM"
- icewm &
- sleep 5
- eend 0
-
- cd "${BUILD_DIR}" || die
- mkdir -p "${HOME}"/.gnupg || die
- for plug in "${plugins[@]}"; do
- edo ./copyq tests PLUGINS:"${plug}"
- done
-
- # ScriptError: Failed to send key presses
- #edo ./copyq tests
-}
-
-src_test() {
- virtx my_src_test
-}
-
-pkg_postinst() {
- xdg_pkg_postinst
- optfeature "encryption support" app-crypt/gnupg
-}
diff --git a/x11-misc/copyq/copyq-7.1.0.ebuild b/x11-misc/copyq/copyq-7.1.0.ebuild
new file mode 100644
index 000000000000..8d39c5de4835
--- /dev/null
+++ b/x11-misc/copyq/copyq-7.1.0.ebuild
@@ -0,0 +1,138 @@
+# Copyright 1999-2023 Gentoo Authors
+# Distributed under the terms of the GNU General Public License v2
+
+EAPI=8
+
+inherit cmake optfeature virtualx xdg
+
+DESCRIPTION="Clipboard manager with advanced features"
+HOMEPAGE="
+ https://hluk.github.io/CopyQ/
+ https://github.com/hluk/CopyQ/
+"
+SRC_URI="https://github.com/hluk/${PN}/archive/v${PV}.tar.gz -> ${P}.tar.gz"
+S="${WORKDIR}/CopyQ-${PV}"
+
+LICENSE="GPL-3+"
+SLOT="0"
+KEYWORDS="~amd64 ~arm64 ~x86 ~amd64-linux ~x86-linux"
+
+IUSE="notification qt6 test"
+# Native notifications are not supported with Qt 6
+# (Bumpers please check when this requirement is lifted).
+# src/notifications.cmake
+REQUIRED_USE="notification? ( !qt6 )"
+
+RDEPEND="
+ dev-libs/wayland
+ x11-libs/libX11
+ x11-libs/libXtst
+ !qt6? (
+ dev-qt/qtcore:5
+ dev-qt/qtdeclarative:5
+ dev-qt/qtgui:5
+ dev-qt/qtnetwork:5
+ dev-qt/qtsvg:5
+ dev-qt/qtwayland:5
+ dev-qt/qtwidgets:5
+ dev-qt/qtx11extras:5
+ notification? ( kde-frameworks/knotifications:5 )
+ test? ( dev-qt/qttest:5 )
+ )
+ qt6? (
+ dev-qt/qtbase:6=[X,gui,network,widgets,xml(+)]
+ dev-qt/qtdeclarative:6
+ dev-qt/qtsvg:6
+ dev-qt/qtwayland:6
+ )
+"
+DEPEND="${RDEPEND}
+ x11-base/xorg-proto
+"
+BDEPEND="
+ kde-frameworks/extra-cmake-modules:0
+ !qt6? (
+ dev-qt/linguist-tools:5
+ dev-qt/qtwaylandscanner:5
+ )
+ qt6? (
+ dev-qt/qttools:6[linguist]
+ dev-util/wayland-scanner
+ )
+ test? (
+ app-crypt/gnupg
+ x11-wm/openbox
+ )
+"
+
+PATCHES=(
+ "${FILESDIR}/copyq-7.1.0-fix-qt-6.6.0-build.patch"
+ "${FILESDIR}/copyq-7.1.0-fix-test-failure-due-to-invalid-regex.patch"
+ "${FILESDIR}/copyq-7.1.0-fix-gpg-2.1-support.patch"
+ "${FILESDIR}/copyq-7.1.0-support-plugin-dir-envvar.patch"
+)
+
+src_prepare() {
+ cmake_src_prepare
+
+ # FAIL! : Tests::actionDialogAccept() 'NO_ERRORS(m_test->runClient((Args() << "keys" << actionDialogId << "ENTER" << clipboardBrowserId), toByteArray("")))' returned FALSE.
+ # FAIL! : Tests::actionDialogSelection() 'NO_ERRORS(m_test->runClient((Args() << "keys" << actionDialogId << "ENTER" << clipboardBrowserId), toByteArray("")))' returned FALSE.
+ # FAIL! : Tests::actionDialogSelectionInputOutput() 'NO_ERRORS(m_test->runClient((Args() << "keys" << actionDialogId << "ENTER" << clipboardBrowserId), toByteArray("")))' returned FALSE.
+ # FAIL! : Tests::commandShowAt() 'NO_ERRORS(m_test->waitOnOutput((Args() << "visible"), toByteArray("true\n")))' returned FALSE.
+ sed -Ei -e '
+ /Tests::(actionDialog(Accept|Selection(|InputOutput))|commandShow)/,/}/ {
+ /^\s*\{/ a \
+ #if QT_VERSION < QT_VERSION_CHECK(6,0,0)\
+ SKIP("Broken on qt5");\
+ #endif
+ }' src/tests/tests.cpp || die
+}
+
+src_configure() {
+ local mycmakeargs=(
+ -DPLUGIN_INSTALL_PREFIX="${EPREFIX}/usr/$(get_libdir)/${PN}/plugins"
+ -DWITH_NATIVE_NOTIFICATIONS=$(usex notification)
+ -DWITH_QT6=$(usex qt6)
+ -DWITH_TESTS=$(usex test)
+ )
+
+ cmake_src_configure
+}
+
+my_src_test() {
+ # Don't rerun tests and more logs
+ local -x COPYQ_TESTS_RERUN_FAILED=0
+ local -x COPYQ_LOG_LEVEL=DEBUG
+
+ # Skip test that require network
+ local -x COPYQ_TESTS_NO_NETWORK=1
+
+ # Less noise from trying the wayland plugin
+ local -x QT_QPA_PLATFORM=xcb
+
+ # Make sure copyq doesn't use system installed plugins which may be incompatible.
+ local -x COPYQ_PLUGIN_DIR="${BUILD_DIR}/plugins"
+
+ # In case the users current system confuses the notification integration
+ unset KDE_FULL_SESSION XDG_CURRENT_DESKTOP
+
+ mkdir "${HOME}"/.gnupg || die
+
+ ebegin "Starting Openbox"
+ openbox & # upstream uses Openbox and it doesn't fail like IceWM
+ sleep 5
+ eend 0
+
+ "${BUILD_DIR}"/copyq tests
+
+ return $?
+}
+
+src_test() {
+ virtx my_src_test
+}
+
+pkg_postinst() {
+ xdg_pkg_postinst
+ optfeature "encryption support" app-crypt/gnupg
+}
diff --git a/x11-misc/copyq/files/copyq-7.1.0-fix-gpg-2.1-support.patch b/x11-misc/copyq/files/copyq-7.1.0-fix-gpg-2.1-support.patch
new file mode 100644
index 000000000000..b06e7e759b84
--- /dev/null
+++ b/x11-misc/copyq/files/copyq-7.1.0-fix-gpg-2.1-support.patch
@@ -0,0 +1,558 @@
+https://github.com/hluk/CopyQ/pull/2471
+https://github.com/hluk/CopyQ/issues/2463
+https://github.com/hluk/CopyQ/commit/a7a891e1f84c6c046a7bfc904c5fc6ebb98dec94
+
+From a7a891e1f84c6c046a7bfc904c5fc6ebb98dec94 Mon Sep 17 00:00:00 2001
+From: Lukas Holecek <hluk@email.cz>
+Date: Wed, 20 Sep 2023 19:42:08 +0200
+Subject: [PATCH] itemencrypted: Fix managing keys with gpg 2.1 and above
+ (#2471)
+
+* itemencrypted: Fix managing keys with gpg 2.1 and above
+
+Fixes #2463, #1208
+
+* Tests: Avoid skipping itemencrypted tests if gpg is not found
+
+* Windows: Fix running itemencrypted plugin tests
+
+* itemencrypted: Fix error logging
+
+* Ensure config directory exists
+
+* itemencrypted: Fix handling native/non-native key paths
+
+* Appveyor: Fix stuck job waiting on gpg-agent
+--- a/plugins/itemencrypted/itemencrypted.cpp
++++ b/plugins/itemencrypted/itemencrypted.cpp
+@@ -57,20 +57,23 @@ bool waitOrTerminate(QProcess *p, int timeoutMs)
+ bool verifyProcess(QProcess *p, int timeoutMs = 30000)
+ {
+ if ( !waitOrTerminate(p, timeoutMs) ) {
+- log( "ItemEncrypt ERROR: Process timed out; stderr: " + p->readAllStandardError(), LogError );
++ log( QStringLiteral("ItemEncrypt: Process timed out; stderr: %1")
++ .arg(QString::fromUtf8(p->readAllStandardError())), LogError );
+ return false;
+ }
+
+ const int exitCode = p->exitCode();
+ if ( p->exitStatus() != QProcess::NormalExit ) {
+- log( "ItemEncrypt ERROR: Failed to run GnuPG: " + p->errorString(), LogError );
++ log( QStringLiteral("ItemEncrypt: Failed to run GnuPG: %1")
++ .arg(p->errorString()), LogError );
+ return false;
+ }
+
+ if (exitCode != 0) {
+ const QString errors = p->readAllStandardError();
+ if ( !errors.isEmpty() )
+- log( "ItemEncrypt ERROR: GnuPG stderr:\n" + errors, LogError );
++ log( QStringLiteral("ItemEncrypt: GnuPG stderr:\n%1")
++ .arg(errors), LogError );
+ return false;
+ }
+
+@@ -88,55 +91,106 @@ QString getGpgVersionOutput(const QString &executable) {
+ return p.readAllStandardOutput();
+ }
+
+-bool checkGpgExecutable(const QString &executable)
++struct GpgVersion {
++ int major;
++ int minor;
++};
++
++GpgVersion parseVersion(const QString &versionOutput)
+ {
+- const auto versionOutput = getGpgVersionOutput(executable);
+- return versionOutput.contains(" 2.");
++ const int lineEndIndex = versionOutput.indexOf('\n');
++#if QT_VERSION < QT_VERSION_CHECK(5,15,2)
++ const QStringRef firstLine = versionOutput.midRef(0, lineEndIndex);
++#else
++ const auto firstLine = QStringView{versionOutput}.mid(0, lineEndIndex);
++#endif
++ const QRegularExpression versionRegex(QStringLiteral(R"( (\d+)\.(\d+))"));
++ const QRegularExpressionMatch match = versionRegex.match(firstLine);
++#if QT_VERSION >= QT_VERSION_CHECK(6,0,0)
++ const int major = match.hasMatch() ? match.capturedView(1).toInt() : 0;
++ const int minor = match.hasMatch() ? match.capturedView(2).toInt() : 0;
++#else
++ const int major = match.hasMatch() ? match.capturedRef(1).toInt() : 0;
++ const int minor = match.hasMatch() ? match.capturedRef(2).toInt() : 0;
++#endif
++ return GpgVersion{major, minor};
+ }
+
++class GpgExecutable {
++public:
++ GpgExecutable() = default;
++
++ explicit GpgExecutable(const QString &executable)
++ : m_executable(executable)
++ {
++ const auto versionOutput = getGpgVersionOutput(executable);
++ if ( !versionOutput.isEmpty() ) {
++ COPYQ_LOG_VERBOSE(
++ QStringLiteral("ItemEncrypt INFO: '%1 --version' output: %2")
++ .arg(executable, versionOutput) );
++
++ const GpgVersion version = parseVersion(versionOutput);
++ m_isSupported = version.major >= 2;
++ COPYQ_LOG( QStringLiteral("ItemEncrypt INFO: %1 gpg version: %2.%3")
++ .arg(m_isSupported ? "Supported" : "Unsupported")
++ .arg(version.major)
++ .arg(version.minor) );
++
++ const bool needsSecring = version.major == 2 && version.minor == 0;
++
++ const QString path = getConfigurationFilePath("");
++ m_pubring = path + ".pub";
++ m_pubringNative = QDir::toNativeSeparators(m_pubring);
++ if (needsSecring) {
++ m_secring = path + ".sec";
++ m_secringNative = QDir::toNativeSeparators(m_secring);
++ }
++
+ #ifdef Q_OS_WIN
+-bool checkUnixGpg(const QString &executable)
+-{
+- static const auto unixGpg = getGpgVersionOutput(executable).contains("Home: /c/");
+- return unixGpg;
+-}
++ const bool isUnixGpg = versionOutput.contains("Home: /c/");
++ if (isUnixGpg) {
++ m_pubringNative = QString(m_pubring).replace(":", "").insert(0, '/');
++ if (needsSecring)
++ m_secringNative = QString(m_secring).replace(":", "").insert(0, '/');
++ }
+ #endif
++ }
++ }
++
++ const QString &executable() const { return m_executable; }
++ bool isSupported() const { return m_isSupported; }
++ bool needsSecring() const { return !m_secring.isEmpty(); }
++ const QString &pubring() const { return m_pubring; }
++ const QString &secring() const { return m_secring; }
++ const QString &pubringNative() const { return m_pubringNative; }
++ const QString &secringNative() const { return m_secringNative; }
++
++private:
++ QString m_executable;
++ QString m_pubring;
++ QString m_secring;
++ QString m_pubringNative;
++ QString m_secringNative;
++ bool m_isSupported = false;
++};
+
+-QString findGpgExecutable()
++GpgExecutable findGpgExecutable()
+ {
+ for (const auto &executable : {"gpg2", "gpg"}) {
+- if ( checkGpgExecutable(executable) )
+- return executable;
++ GpgExecutable gpg(executable);
++ if ( gpg.isSupported() )
++ return gpg;
+ }
+
+- return QString();
++ return GpgExecutable();
+ }
+
+-const QString &gpgExecutable()
++const GpgExecutable &gpgExecutable()
+ {
+ static const auto gpg = findGpgExecutable();
+ return gpg;
+ }
+
+-struct KeyPairPaths {
+- KeyPairPaths()
+- {
+- const QString path = getConfigurationFilePath("");
+- sec = QDir::toNativeSeparators(path + ".sec");
+- pub = QDir::toNativeSeparators(path + ".pub");
+-
+-#ifdef Q_OS_WIN
+- if (checkUnixGpg(gpgExecutable())) {
+- pub = QDir::fromNativeSeparators(pub).replace(":", "").insert(0, '/');
+- sec = QDir::fromNativeSeparators(sec).replace(":", "").insert(0, '/');
+- }
+-#endif
+- }
+-
+- QString sec;
+- QString pub;
+-};
+-
+ QStringList getDefaultEncryptCommandArguments(const QString &publicKeyPath)
+ {
+ return QStringList() << "--trust-model" << "always" << "--recipient" << "copyq"
+@@ -146,16 +200,18 @@ QStringList getDefaultEncryptCommandArguments(const QString &publicKeyPath)
+
+ void startGpgProcess(QProcess *p, const QStringList &args, QIODevice::OpenModeFlag mode)
+ {
+- KeyPairPaths keys;
+- p->start(gpgExecutable(), getDefaultEncryptCommandArguments(keys.pub) + args, mode);
++ const auto &gpg = gpgExecutable();
++ p->start(gpg.executable(), getDefaultEncryptCommandArguments(gpg.pubringNative()) + args, mode);
+ }
+
+ QString importGpgKey()
+ {
+- KeyPairPaths keys;
++ const auto &gpg = gpgExecutable();
++ if ( !gpg.needsSecring() )
++ return QString();
+
+ QProcess p;
+- p.start(gpgExecutable(), getDefaultEncryptCommandArguments(keys.pub) << "--import" << keys.sec);
++ p.start(gpg.executable(), getDefaultEncryptCommandArguments(gpg.pubringNative()) << "--import" << gpg.secringNative());
+ if ( !verifyProcess(&p) )
+ return "Failed to import private key (see log).";
+
+@@ -164,18 +220,20 @@ QString importGpgKey()
+
+ QString exportGpgKey()
+ {
+- KeyPairPaths keys;
++ const auto &gpg = gpgExecutable();
++ if ( !gpg.needsSecring() )
++ return QString();
+
+ // Private key already created or exported.
+- if ( QFile::exists(keys.sec) )
++ if ( QFile::exists(gpg.secring()) )
+ return QString();
+
+ QProcess p;
+- p.start(gpgExecutable(), getDefaultEncryptCommandArguments(keys.pub) << "--export-secret-key" << "copyq");
++ p.start(gpg.executable(), getDefaultEncryptCommandArguments(gpg.pubringNative()) << "--export-secret-key" << gpg.secringNative());
+ if ( !verifyProcess(&p) )
+ return "Failed to export private key (see log).";
+
+- QFile secKey(keys.sec);
++ QFile secKey(gpg.secring());
+ if ( !secKey.open(QIODevice::WriteOnly) )
+ return "Failed to create private key.";
+
+@@ -240,7 +298,7 @@ bool encryptMimeData(const QVariantMap &data, const QModelIndex &index, QAbstrac
+
+ void startGenerateKeysProcess(QProcess *process, bool useTransientPasswordlessKey = false)
+ {
+- const KeyPairPaths keys;
++ const auto &gpg = gpgExecutable();
+
+ auto args = QStringList() << "--batch" << "--gen-key";
+
+@@ -253,15 +311,19 @@ void startGenerateKeysProcess(QProcess *process, bool useTransientPasswordlessKe
+ }
+
+ startGpgProcess(process, args, QIODevice::ReadWrite);
+- process->write( "\nKey-Type: RSA"
+- "\nKey-Usage: encrypt"
+- "\nKey-Length: 4096"
+- "\nName-Real: copyq"
+- + transientOptions +
+- "\n%secring " + keys.sec.toUtf8() +
+- "\n%pubring " + keys.pub.toUtf8() +
+- "\n%commit"
+- "\n" );
++ process->write(
++ "\nKey-Type: RSA"
++ "\nKey-Usage: encrypt"
++ "\nKey-Length: 4096"
++ "\nName-Real: copyq"
++ + transientOptions +
++ "\n%pubring " + gpg.pubringNative().toUtf8()
++ );
++
++ if ( gpg.needsSecring() )
++ process->write("\n%secring " + gpg.secringNative().toUtf8());
++
++ process->write("\n%commit\n");
+ process->closeWriteChannel();
+ }
+
+@@ -276,7 +338,7 @@ QString exportImportGpgKeys()
+
+ bool isGpgInstalled()
+ {
+- return !gpgExecutable().isEmpty();
++ return gpgExecutable().isSupported();
+ }
+
+ } // namespace
+@@ -314,7 +376,7 @@ bool ItemEncryptedSaver::saveItems(const QString &, const QAbstractItemModel &mo
+ bytes = readGpgOutput(QStringList("--encrypt"), bytes);
+ if ( bytes.isEmpty() ) {
+ emitEncryptFailed();
+- COPYQ_LOG("ItemEncrypt ERROR: Failed to read encrypted data");
++ log("ItemEncrypt: Failed to read encrypted data", LogError);
+ return false;
+ }
+
+@@ -325,7 +387,7 @@ bool ItemEncryptedSaver::saveItems(const QString &, const QAbstractItemModel &mo
+
+ if ( stream.status() != QDataStream::Ok ) {
+ emitEncryptFailed();
+- COPYQ_LOG("ItemEncrypt ERROR: Failed to write encrypted data");
++ log("ItemEncrypt: Failed to write encrypted data", LogError);
+ return false;
+ }
+
+@@ -510,17 +572,22 @@ void ItemEncryptedScriptable::pasteEncryptedItems()
+
+ QString ItemEncryptedScriptable::generateTestKeys()
+ {
+- const KeyPairPaths keys;
+- for ( const auto &keyFileName : {keys.sec, keys.pub} ) {
++ const auto &gpg = gpgExecutable();
++
++ const QStringList keys = gpg.needsSecring()
++ ? QStringList{gpg.pubring(), gpg.secring()}
++ : QStringList{gpg.pubring()};
++
++ for (const auto &keyFileName : keys) {
+ if ( QFile::exists(keyFileName) && !QFile::remove(keyFileName) )
+- return QString("Failed to remove \"%1\"").arg(keys.sec);
++ return QString("Failed to remove \"%1\"").arg(keyFileName);
+ }
+
+ QProcess process;
+ startGenerateKeysProcess(&process, true);
+
+ if ( !verifyProcess(&process) ) {
+- return QString("ItemEncrypt ERROR: %1; stderr: %2")
++ return QString("ItemEncrypt: %1; stderr: %2")
+ .arg( process.errorString(),
+ QString::fromUtf8(process.readAllStandardError()) );
+ }
+@@ -529,9 +596,9 @@ QString ItemEncryptedScriptable::generateTestKeys()
+ if ( !error.isEmpty() )
+ return error;
+
+- for ( const auto &keyFileName : {keys.sec, keys.pub} ) {
++ for (const auto &keyFileName : keys) {
+ if ( !QFile::exists(keyFileName) )
+- return QString("Failed to create \"%1\"").arg(keys.sec);
++ return QString("Failed to create \"%1\"").arg(keyFileName);
+ }
+
+ return QString();
+@@ -606,19 +673,29 @@ QWidget *ItemEncryptedLoader::createSettingsWidget(QWidget *parent)
+ m_encryptTabs.join('\n') );
+
+ if (status() != GpgNotInstalled) {
+- KeyPairPaths keys;
++ const auto &gpg = gpgExecutable();
+ ui->labelShareInfo->setTextFormat(Qt::RichText);
+- ui->labelShareInfo->setText( ItemEncryptedLoader::tr(
+- "To share encrypted items on other computer or"
+- " session, you'll need public and secret key files:"
+- "<ul>"
+- "<li>%1</li>"
+- "<li>%2<br />(Keep this secret key in a safe place.)</li>"
+- "</ul>"
+- )
+- .arg( quoteString(keys.pub),
+- quoteString(keys.sec) )
+- );
++ QString text = ItemEncryptedLoader::tr(
++ "To share encrypted items on other computer or"
++ " session, you'll need these secret key files (keep them in a safe place):"
++ );
++ if (gpg.needsSecring()) {
++ text.append( QStringLiteral(
++ "<ul>"
++ "<li>%1</li>"
++ "<li>%2</li>"
++ "</ul>"
++ ).arg(quoteString(gpg.pubringNative()), quoteString(gpg.secringNative()))
++ );
++ } else {
++ text.append( QStringLiteral(
++ "<ul>"
++ "<li>%1</li>"
++ "</ul>"
++ ).arg(quoteString(gpg.pubringNative()))
++ );
++ }
++ ui->labelShareInfo->setText(text);
+ }
+
+ updateUi();
+@@ -689,7 +766,7 @@ ItemSaverPtr ItemEncryptedLoader::loadItems(const QString &, QAbstractItemModel
+ const int bytesRead = stream.readRawData(encryptedBytes, 4096);
+ if (bytesRead == -1) {
+ emitDecryptFailed();
+- COPYQ_LOG("ItemEncrypted ERROR: Failed to read encrypted data");
++ log("ItemEncrypted: Failed to read encrypted data", LogError);
+ return nullptr;
+ }
+ p.write(encryptedBytes, bytesRead);
+@@ -708,7 +785,7 @@ ItemSaverPtr ItemEncryptedLoader::loadItems(const QString &, QAbstractItemModel
+ const QByteArray bytes = p.readAllStandardOutput();
+ if ( bytes.isEmpty() ) {
+ emitDecryptFailed();
+- COPYQ_LOG("ItemEncrypt ERROR: Failed to read encrypted data.");
++ log("ItemEncrypt: Failed to read encrypted data", LogError);
+ verifyProcess(&p);
+ return nullptr;
+ }
+@@ -719,7 +796,7 @@ ItemSaverPtr ItemEncryptedLoader::loadItems(const QString &, QAbstractItemModel
+ stream2 >> length;
+ if ( stream2.status() != QDataStream::Ok ) {
+ emitDecryptFailed();
+- COPYQ_LOG("ItemEncrypt ERROR: Failed to parse item count!");
++ log("ItemEncrypt: Failed to parse item count", LogError);
+ return nullptr;
+ }
+ length = qMin(length, static_cast<quint64>(maxItems)) - static_cast<quint64>(model->rowCount());
+@@ -728,7 +805,7 @@ ItemSaverPtr ItemEncryptedLoader::loadItems(const QString &, QAbstractItemModel
+ for ( int i = 0; i < count && stream2.status() == QDataStream::Ok; ++i ) {
+ if ( !model->insertRow(i) ) {
+ emitDecryptFailed();
+- COPYQ_LOG("ItemEncrypt ERROR: Failed to insert item!");
++ log("ItemEncrypt: Failed to insert item", LogError);
+ return nullptr;
+ }
+ QVariantMap dataMap;
+@@ -738,7 +815,7 @@ ItemSaverPtr ItemEncryptedLoader::loadItems(const QString &, QAbstractItemModel
+
+ if ( stream2.status() != QDataStream::Ok ) {
+ emitDecryptFailed();
+- COPYQ_LOG("ItemEncrypt ERROR: Failed to decrypt item!");
++ log("ItemEncrypt: Failed to decrypt item", LogError);
+ return nullptr;
+ }
+
+--- a/plugins/itemencrypted/tests/itemencryptedtests.cpp
++++ b/plugins/itemencrypted/tests/itemencryptedtests.cpp
+@@ -25,6 +25,8 @@ void ItemEncryptedTests::cleanupTestCase()
+ void ItemEncryptedTests::init()
+ {
+ TEST(m_test->init());
++
++ QVERIFY(isGpgInstalled());
+ }
+
+ void ItemEncryptedTests::cleanup()
+@@ -34,13 +36,10 @@ void ItemEncryptedTests::cleanup()
+
+ void ItemEncryptedTests::encryptDecryptData()
+ {
+- if ( !isGpgInstalled() )
+- SKIP("gpg2 is required to run the test");
+-
+- RUN("-e" << "plugins.itemencrypted.generateTestKeys()", "\n");
++ RUN("plugins.itemencrypted.generateTestKeys()", "\n");
+
+ // Test gpg errors first.
+- RUN("-e" << "plugins.itemencrypted.encrypt(input());print('')", "");
++ RUN("plugins.itemencrypted.encrypt(input());print('')", "");
+
+ const QByteArray input("\x00\x01\x02\x03\x04", 5);
+ QByteArray stdoutActual;
+@@ -60,10 +59,7 @@ void ItemEncryptedTests::encryptDecryptItems()
+ SKIP("Ctrl+L shortcut doesn't seem work on OS X");
+ #endif
+
+- if ( !isGpgInstalled() )
+- SKIP("gpg2 is required to run the test");
+-
+- RUN("-e" << "plugins.itemencrypted.generateTestKeys()", "\n");
++ RUN("plugins.itemencrypted.generateTestKeys()", "\n");
+
+ // Load commands from the plugin generating keys.
+ RUN("keys" << "Ctrl+P" << "ENTER", "");
+--- a/src/app/clipboardserver.cpp
++++ b/src/app/clipboardserver.cpp
+@@ -124,6 +124,8 @@ ClipboardServer::ClipboardServer(QApplication *app, const QString &sessionName)
+
+ QApplication::setQuitOnLastWindowClosed(false);
+
++ ensureSettingsDirectoryExists();
++
+ m_sharedData = std::make_shared<ClipboardBrowserShared>();
+ m_sharedData->itemFactory = new ItemFactory(this);
+ m_sharedData->notifications = new NotificationDaemon(this);
+--- a/src/common/config.cpp
++++ b/src/common/config.cpp
+@@ -157,6 +157,20 @@ QString getConfigurationFilePathHelper()
+
+ } // namespace
+
++bool ensureSettingsDirectoryExists()
++{
++ QDir settingsDir( settingsDirectoryPath() );
++ if ( !settingsDir.mkpath(".") ) {
++ log( QStringLiteral("Failed to create the directory for settings: %1")
++ .arg(settingsDir.path()),
++ LogError );
++
++ return false;
++ }
++
++ return true;
++}
++
+ const QString &getConfigurationFilePath()
+ {
+ static const QString path = getConfigurationFilePathHelper();
+--- a/src/common/config.h
++++ b/src/common/config.h
+@@ -9,6 +9,8 @@ class QString;
+ class QVariant;
+ class QWidget;
+
++bool ensureSettingsDirectoryExists();
++
+ const QString &getConfigurationFilePath();
+
+ QString getConfigurationFilePath(const char *suffix);
+--- a/src/item/itemstore.cpp
++++ b/src/item/itemstore.cpp
+@@ -22,20 +22,6 @@ QString itemFileName(const QString &id)
+ return getConfigurationFilePath("_tab_") + part + QLatin1String(".dat");
+ }
+
+-bool createItemDirectory()
+-{
+- QDir settingsDir( settingsDirectoryPath() );
+- if ( !settingsDir.mkpath(".") ) {
+- log( QString("Cannot create directory for settings %1!")
+- .arg(quoteString(settingsDir.path()) ),
+- LogError );
+-
+- return false;
+- }
+-
+- return true;
+-}
+-
+ void printItemFileError(
+ const QString &action, const QString &id, const QFileDevice &file)
+ {
+@@ -83,9 +69,6 @@ ItemSaverPtr createTab(
+
+ ItemSaverPtr loadItems(const QString &tabName, QAbstractItemModel &model, ItemFactory *itemFactory, int maxItems)
+ {
+- if ( !createItemDirectory() )
+- return nullptr;
+-
+ const QString tabFileName = itemFileName(tabName);
+ if ( !QFile::exists(tabFileName) )
+ return createTab(tabName, model, itemFactory, maxItems);
+@@ -107,7 +90,7 @@ bool saveItems(const QString &tabName, const QAbstractItemModel &model, const It
+ {
+ const QString tabFileName = itemFileName(tabName);
+
+- if ( !createItemDirectory() )
++ if ( !ensureSettingsDirectoryExists() )
+ return false;
+
+ // Save tab data to a new temporary file.
diff --git a/x11-misc/copyq/files/copyq-7.1.0-fix-qt-6.6.0-build.patch b/x11-misc/copyq/files/copyq-7.1.0-fix-qt-6.6.0-build.patch
new file mode 100644
index 000000000000..2b149ab843bf
--- /dev/null
+++ b/x11-misc/copyq/files/copyq-7.1.0-fix-qt-6.6.0-build.patch
@@ -0,0 +1,44 @@
+https://bugs.gentoo.org/916129
+https://github.com/hluk/CopyQ/pull/2508
+https://github.com/hluk/CopyQ/commit/19e9dd1c2ecb49b14a24159c5ac3bc1b77fdf250
+
+From 19e9dd1c2ecb49b14a24159c5ac3bc1b77fdf250 Mon Sep 17 00:00:00 2001
+From: Nick Cao <nickcao@nichi.co>
+Date: Tue, 17 Oct 2023 02:08:51 -0400
+Subject: [PATCH] itemfakevim: fix build with qt 6.6.0 (#2508)
+
+Reference: https://github.com/qt-creator/qt-creator/commit/e56e3b6f374e00179eb0537198437864dddc47f2
+--- a/plugins/itemfakevim/fakevim/fakevimhandler.cpp
++++ b/plugins/itemfakevim/fakevim/fakevimhandler.cpp
+@@ -1057,14 +1057,6 @@ inline QString msgMarkNotSet(const QString &text)
+ return Tr::tr("Mark \"%1\" not set.").arg(text);
+ }
+
+-static void initSingleShotTimer(QTimer *timer, int interval, FakeVimHandler::Private *receiver,
+- void (FakeVimHandler::Private::*slot)())
+-{
+- timer->setSingleShot(true);
+- timer->setInterval(interval);
+- QObject::connect(timer, &QTimer::timeout, receiver, slot);
+-}
+-
+ class Input
+ {
+ public:
+@@ -2424,6 +2416,16 @@ class FakeVimHandler::Private : public QObject
+ FakeVimSettings &s = *fakeVimSettings();
+ };
+
++static void initSingleShotTimer(QTimer *timer,
++ int interval,
++ FakeVimHandler::Private *receiver,
++ void (FakeVimHandler::Private::*slot)())
++{
++ timer->setSingleShot(true);
++ timer->setInterval(interval);
++ QObject::connect(timer, &QTimer::timeout, receiver, slot);
++}
++
+ FakeVimHandler::Private::GlobalData FakeVimHandler::Private::g;
+
+ FakeVimHandler::Private::Private(FakeVimHandler *parent, QWidget *widget)
diff --git a/x11-misc/copyq/files/copyq-7.1.0-fix-test-failure-due-to-invalid-regex.patch b/x11-misc/copyq/files/copyq-7.1.0-fix-test-failure-due-to-invalid-regex.patch
new file mode 100644
index 000000000000..e526f3a89f0b
--- /dev/null
+++ b/x11-misc/copyq/files/copyq-7.1.0-fix-test-failure-due-to-invalid-regex.patch
@@ -0,0 +1,98 @@
+https://github.com/hluk/CopyQ/commit/42c02f2dc74b188ea7982a30c38acaf668bbf76a
+
+From 42c02f2dc74b188ea7982a30c38acaf668bbf76a Mon Sep 17 00:00:00 2001
+From: Lukas Holecek <hluk@email.cz>
+Date: Mon, 4 Sep 2023 21:12:44 +0200
+Subject: [PATCH] Avoid showing warnings about invalid regex
+
+--- a/src/scriptable/scriptableitemselection.cpp
++++ b/src/scriptable/scriptableitemselection.cpp
+@@ -46,10 +46,6 @@ QVector<int> toIntVector(const QJSValue &value)
+
+ QRegularExpression toRegularExpression(const QJSValue &value)
+ {
+- // If argument is invalid/not-regexp, create an invalid regex to match nothing.
+- if ( !value.isRegExp() )
+- return QRegularExpression("(");
+-
+ const QVariant variant = value.toVariant();
+ QRegularExpression regexp = variant.toRegularExpression();
+
+@@ -136,7 +132,7 @@ QJSValue ScriptableItemSelection::selectAll()
+
+ QJSValue ScriptableItemSelection::select(const QJSValue &re, const QString &mimeFormat)
+ {
+- const QVariant regexp = re.isUndefined() ? QVariant() : toRegularExpression(re);
++ const QVariant regexp = re.isRegExp() ? toRegularExpression(re) : QVariant();
+ m_proxy->selectionSelect(m_id, regexp, mimeFormat);
+ return m_self;
+ }
+--- a/src/tests/testinterface.h
++++ b/src/tests/testinterface.h
+@@ -85,9 +85,6 @@ class TestInterface {
+ /// Clean up tabs and items. Return error string on error.
+ virtual QByteArray cleanup() = 0;
+
+- /// Ignore given text in logs for current unit test.
+- virtual void setIgnoreError(const QByteArray &ignoreError) = 0;
+-
+ /// Platform specific key to remove (usually Delete, Backspace on OS X).
+ virtual QString shortcutToRemove() = 0;
+
+--- a/src/tests/tests.cpp
++++ b/src/tests/tests.cpp
+@@ -150,8 +150,6 @@ bool testStderr(const QByteArray &stderrData, TestInterface::ReadStderrFlag flag
+ // Ignore exceptions and errors from clients in application log
+ // (these are expected in some tests).
+ static const std::vector<QRegularExpression> ignoreList{
+- plain("[EXPECTED-IN-TEST]"),
+-
+ regex(R"(CopyQ Note \[\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d{3}\] <Client-[^\n]*)"),
+
+ // X11 (Linux)
+@@ -520,8 +518,6 @@ class TestInterfaceImpl final : public TestInterface {
+ if (m_server) {
+ QCoreApplication::processEvents();
+ QByteArray output = readLogFile(maxReadLogSize);
+- if ( !m_ignoreError.isEmpty() )
+- output.replace(m_ignoreError, "[EXPECTED-IN-TEST] " + m_ignoreError);
+ if ( flag == ReadAllStderr || !testStderr(output, flag) )
+ return decorateOutput("Server STDERR", output);
+ }
+@@ -645,16 +641,10 @@ class TestInterfaceImpl final : public TestInterface {
+
+ QByteArray cleanup() override
+ {
+- m_ignoreError.clear();
+ addFailedTest();
+ return QByteArray();
+ }
+
+- void setIgnoreError(const QByteArray &ignoreError) override
+- {
+- m_ignoreError = ignoreError;
+- }
+-
+ QString shortcutToRemove() override
+ {
+ return ::shortcutToRemove();
+@@ -771,8 +761,6 @@ class TestInterfaceImpl final : public TestInterface {
+ QStringList m_failed;
+
+ PlatformClipboardPtr m_clipboard;
+-
+- QByteArray m_ignoreError;
+ };
+
+ QString keyNameFor(QKeySequence::StandardKey standardKey)
+@@ -2272,9 +2260,8 @@ void Tests::classItemSelection()
+ RUN(args << "ItemSelection().select(undefined, mimeItemNotes).str()", outRows.arg("0,2"));
+
+ // Match nothing if select() argument is not a regular expression.
+- m_test->setIgnoreError("QtWarning: QString::contains: invalid QRegularExpression object");
++ RUN(args << "add" << "", "");
+ RUN(args << "ItemSelection().select('A').str()", outRows.arg(""));
+- m_test->setIgnoreError(QByteArray());
+ }
+
+ void Tests::classItemSelectionGetCurrent()
diff --git a/x11-misc/copyq/files/copyq-7.1.0-support-plugin-dir-envvar.patch b/x11-misc/copyq/files/copyq-7.1.0-support-plugin-dir-envvar.patch
new file mode 100644
index 000000000000..21c60f87011b
--- /dev/null
+++ b/x11-misc/copyq/files/copyq-7.1.0-support-plugin-dir-envvar.patch
@@ -0,0 +1,26 @@
+From 32b45b42f0d9dbdaae077f81d11fff7bd2455492 Mon Sep 17 00:00:00 2001
+From: Alfred Wingate <parona@protonmail.com>
+Date: Wed, 6 Dec 2023 06:16:36 +0200
+Subject: [PATCH] itemfactory: Add support for setting plugin dir in the
+ environment
+
+Signed-off-by: Alfred Wingate <parona@protonmail.com>
+--- a/src/item/itemfactory.cpp
++++ b/src/item/itemfactory.cpp
+@@ -31,6 +31,13 @@ namespace {
+
+ bool findPluginDir(QDir *pluginsDir)
+ {
++ QString pluginDirEnv = qEnvironmentVariable("COPYQ_PLUGIN_DIR");
++ if ( !pluginDirEnv.isEmpty() )
++ pluginsDir->setPath(pluginDirEnv);
++
++ if ( pluginsDir->isReadable() )
++ return true;
++
+ #ifdef COPYQ_PLUGIN_PREFIX
+ pluginsDir->setPath(COPYQ_PLUGIN_PREFIX);
+ if ( pluginsDir->isReadable() )
+--
+2.43.0
+
diff --git a/x11-misc/copyq/metadata.xml b/x11-misc/copyq/metadata.xml
index 7606b24718c3..195b1a447114 100644
--- a/x11-misc/copyq/metadata.xml
+++ b/x11-misc/copyq/metadata.xml
@@ -3,6 +3,10 @@
<pkgmetadata>
<!-- maintainer-needed -->
<upstream>
+ <doc>https://copyq.readthedocs.io/</doc>
<remote-id type="github">hluk/CopyQ</remote-id>
</upstream>
+ <use>
+ <flag name="notification">Build with native notification support (requires <pkg>kde-frameworks/knotifications</pkg>:5)</flag>
+ </use>
</pkgmetadata>