diff --git a/docs/scripting-api.rst b/docs/scripting-api.rst index b497375ac2..5140e47e52 100644 --- a/docs/scripting-api.rst +++ b/docs/scripting-api.rst @@ -2208,6 +2208,12 @@ These MIME types values are assigned to global variables prefixed with copyq copy application/x-copyq-hidden 1 plain/text "This is secret" +.. js:data:: mimeSecret + + If set to ``1``, the clipboard contains a password or other secret (for example, copied from clipboard manager). + + In such case, the data won't be available in the app, not even via calling ``data()`` script function. + .. js:data:: mimeShortcut Application or global shortcut which activated the command. Value: 'application/x-copyq-shortcut'. diff --git a/src/app/clipboardmonitor.cpp b/src/app/clipboardmonitor.cpp index 3e59ca3e87..8575bfd3c1 100644 --- a/src/app/clipboardmonitor.cpp +++ b/src/app/clipboardmonitor.cpp @@ -22,30 +22,15 @@ namespace { bool hasSameData(const QVariantMap &data, const QVariantMap &lastData) { - // Detect change also in case the data is unchanged but previously copied - // by CopyQ and now externally. This solves storing a copied text which was - // previously synchronized from selection to clipboard via CopyQ. - if ( - !lastData.value(mimeOwner).toByteArray().isEmpty() - && data.value(mimeOwner).toByteArray().isEmpty() - ) - { - return false; - } - for (auto it = lastData.constBegin(); it != lastData.constEnd(); ++it) { const auto &format = it.key(); - if ( !format.startsWith(COPYQ_MIME_PREFIX) - && !data.contains(format) ) - { + if ( !data.contains(format) ) return false; - } } for (auto it = data.constBegin(); it != data.constEnd(); ++it) { const auto &format = it.key(); - if ( !format.startsWith(COPYQ_MIME_PREFIX) - && !data[format].toByteArray().isEmpty() + if ( !data[format].toByteArray().isEmpty() && data[format] != lastData.value(format) ) { return false; diff --git a/src/common/common.cpp b/src/common/common.cpp index 9fc88cd5ce..3658b3b25c 100644 --- a/src/common/common.cpp +++ b/src/common/common.cpp @@ -616,6 +616,9 @@ QString textLabelForData(const QVariantMap &data, const QFont &font, const QStri label = QObject::tr("", "Label for image in clipboard"); } else if ( data.contains(mimeItems) ) { label = QObject::tr("", "Label for copied items in clipboard"); + } else if ( data.contains(mimeSecret) ) { + label = QObject::tr( + "", "Label for clipboard content copied from password managers"); } else if ( findFormatsWithPrefix(false, COPYQ_MIME_PREFIX, data) ) { label = QObject::tr("", "Label for empty clipboard"); } else { diff --git a/src/common/mimetypes.cpp b/src/common/mimetypes.cpp index d00e8ebdc3..e1507bac66 100644 --- a/src/common/mimetypes.cpp +++ b/src/common/mimetypes.cpp @@ -23,3 +23,4 @@ const QLatin1String mimeShortcut(COPYQ_MIME_PREFIX "shortcut"); const QLatin1String mimeColor(COPYQ_MIME_PREFIX "color"); const QLatin1String mimeOutputTab(COPYQ_MIME_PREFIX "output-tab"); const QLatin1String mimeDisplayItemInMenu(COPYQ_MIME_PREFIX "display-item-in-menu"); +const QLatin1String mimeSecret(COPYQ_MIME_PREFIX "secret"); diff --git a/src/common/mimetypes.h b/src/common/mimetypes.h index 427f0339a8..e0fd636093 100644 --- a/src/common/mimetypes.h +++ b/src/common/mimetypes.h @@ -26,3 +26,4 @@ extern const QLatin1String mimeShortcut; extern const QLatin1String mimeColor; extern const QLatin1String mimeOutputTab; extern const QLatin1String mimeDisplayItemInMenu; +extern const QLatin1String mimeSecret; diff --git a/src/gui/commandcompleterdocumentation.h b/src/gui/commandcompleterdocumentation.h index 3e867d3f81..578f42e0df 100644 --- a/src/gui/commandcompleterdocumentation.h +++ b/src/gui/commandcompleterdocumentation.h @@ -117,7 +117,7 @@ void addDocumentation(AddDocumentationCallback addDocumentation) addDocumentation("sha256sum", "sha256sum(data) -> `ByteArray`", "Returns SHA256 checksum of data."); addDocumentation("sha512sum", "sha512sum(data) -> `ByteArray`", "Returns SHA512 checksum of data."); addDocumentation("open", "open(url, ...) -> bool", "Tries to open URLs in appropriate applications."); - addDocumentation("execute", "execute(argument, ..., null, stdinData, ...) -> `FinishedCommand` or `undefined`", "Executes a command."); + addDocumentation("execute", "execute(argument, ..., null, stdinData, ...) -> `FinishedCommand`", "Executes a command."); addDocumentation("currentWindowTitle", "String currentWindowTitle() -> string", "Returns window title of currently focused window."); addDocumentation("currentClipboardOwner", "String currentClipboardOwner() -> string", "Returns name of the current clipboard owner."); addDocumentation("dialog", "dialog(...)", "Shows messages or asks user for input."); @@ -204,6 +204,7 @@ void addDocumentation(AddDocumentationCallback addDocumentation) addDocumentation("mimeSelectedItems", "mimeSelectedItems", "Selected items when invoking command from main window. Value: 'application/x-copyq-selected-items'."); addDocumentation("mimeCurrentItem", "mimeCurrentItem", "Current item when invoking command from main window. Value: 'application/x-copyq-current-item'."); addDocumentation("mimeHidden", "mimeHidden", "If set to `1`, the clipboard or item content will be hidden in GUI. Value: 'application/x-copyq-hidden'."); + addDocumentation("mimeSecret", "mimeSecret", "If set to `1`, the clipboard contains a password or other secret (for example, copied from clipboard manager)."); addDocumentation("mimeShortcut", "mimeShortcut", "Application or global shortcut which activated the command. Value: 'application/x-copyq-shortcut'."); addDocumentation("mimeColor", "mimeColor", "Item color (same as the one used by themes). Value: 'application/x-copyq-color'."); addDocumentation("mimeOutputTab", "mimeOutputTab", "Name of the tab where to store new item. Value: 'application/x-copyq-output-tab'."); diff --git a/src/platform/dummy/dummyclipboard.cpp b/src/platform/dummy/dummyclipboard.cpp index e68596e96f..867c6bd468 100644 --- a/src/platform/dummy/dummyclipboard.cpp +++ b/src/platform/dummy/dummyclipboard.cpp @@ -10,6 +10,17 @@ #include #include +namespace { + +const QMimeData *createSecretData() +{ + auto data = new QMimeData(); + data->setData(mimeSecret, QByteArrayLiteral("1")); + return data; +} + +} // namespace + QClipboard::Mode modeToQClipboardMode(ClipboardMode mode) { switch (mode) { @@ -59,7 +70,8 @@ const QMimeData *DummyClipboard::mimeData(ClipboardMode mode) const if (isHidden(*data)) { log( QStringLiteral("Hiding secret %1 data").arg(modeText) ); - return nullptr; + static const QMimeData *secretData = createSecretData(); + return secretData; } COPYQ_LOG_VERBOSE( QStringLiteral("Got %1 data").arg(modeText) ); diff --git a/src/platform/x11/x11platformclipboard.cpp b/src/platform/x11/x11platformclipboard.cpp index eb3b7dc3f7..13d69478a8 100644 --- a/src/platform/x11/x11platformclipboard.cpp +++ b/src/platform/x11/x11platformclipboard.cpp @@ -269,11 +269,6 @@ void X11PlatformClipboard::updateClipboardData(X11PlatformClipboard::ClipboardDa } clipboardData->retry = 0; - // Ignore clipboard with secrets - const QByteArray passwordManagerHint = data->data(QStringLiteral("x-kde-passwordManagerHint")); - if ( passwordManagerHint == QByteArrayLiteral("secret") ) - return; - const QByteArray newDataTimestampData = data->data(QStringLiteral("TIMESTAMP")); quint32 newDataTimestamp = 0; if ( !newDataTimestampData.isEmpty() ) { diff --git a/src/scriptable/scriptable.h b/src/scriptable/scriptable.h index c9a8975c78..23b0206410 100644 --- a/src/scriptable/scriptable.h +++ b/src/scriptable/scriptable.h @@ -52,6 +52,7 @@ class Scriptable final : public QObject Q_PROPERTY(QJSValue mimeColor READ getMimeColor CONSTANT) Q_PROPERTY(QJSValue mimeOutputTab READ getMimeOutputTab CONSTANT) Q_PROPERTY(QJSValue mimeDisplayItemInMenu READ getMimeDisplayItemInMenu CONSTANT) + Q_PROPERTY(QJSValue mimeSecret READ getMimeSecret CONSTANT) Q_PROPERTY(QJSValue plugins READ getPlugins CONSTANT) @@ -138,6 +139,7 @@ class Scriptable final : public QObject QJSValue getMimeColor() const { return mimeColor; } QJSValue getMimeOutputTab() const { return mimeOutputTab; } QJSValue getMimeDisplayItemInMenu() const { return mimeDisplayItemInMenu; } + QJSValue getMimeSecret() const { return mimeSecret; } QJSValue getPlugins(); diff --git a/src/tests/tests.cpp b/src/tests/tests.cpp index 2ec0172b8f..0a51b098e1 100644 --- a/src/tests/tests.cpp +++ b/src/tests/tests.cpp @@ -816,6 +816,25 @@ QString appWindowTitle(const QString &text) #endif } +QVariantMap secretData(const QByteArray &text) +{ +#ifdef Q_OS_WIN + const QString format("application/x-qt-windows-mime;value=\"Clipboard Viewer Ignore\""); + const QByteArray value(""); +#elif defined(Q_OS_MACOS) + const QString format("application/x-nspasteboard-concealed-type"); + const QByteArray value("secret"); +#elif defined(Q_OS_UNIX) + const QString format("x-kde-passwordManagerHint"); + const QByteArray value("secret"); +#endif + + return QVariantMap{ + {format, value}, + {mimeText, text}, + }; +} + } // namespace Tests::Tests(const TestInterfacePtr &test, QObject *parent) @@ -4799,31 +4818,33 @@ void Tests::startServerAndRunCommand() void Tests::avoidStoringPasswords() { -#ifdef Q_OS_WIN - const QString format("application/x-qt-windows-mime;value=\"Clipboard Viewer Ignore\""); - const QByteArray value(""); -#elif defined(Q_OS_MACOS) - const QString format("application/x-nspasteboard-concealed-type"); - const QByteArray value("secret"); -#elif defined(Q_OS_UNIX) - const QString format("x-kde-passwordManagerHint"); - const QByteArray value("secret"); -#endif - - const QVariantMap data{ - {format, value}, - {mimeText, QByteArrayLiteral("secret")}, - }; - TEST( m_test->setClipboard(data) ); + TEST( m_test->setClipboard(secretData("secret")) ); waitFor(2 * waitMsPasteClipboard); - RUN("clipboard" << "?", ""); + RUN("clipboard" << "?", mimeSecret + "\n"); RUN("read" << "0" << "1" << "2", "\n\n"); RUN("count", "0\n"); RUN("keys" << clipboardBrowserId << keyNameFor(QKeySequence::Paste), ""); waitFor(waitMsPasteClipboard); RUN("read" << "0" << "1" << "2", "\n\n"); - RUN("count", "0\n"); + RUN("count", "1\n"); +} + +void Tests::automaticCommandForPasswords() +{ + const auto script = R"( + setCommands([{ + automatic: true, + input: mimeSecret, + cmd: 'copyq: setData(mimeText, "SECRET")' + }]) + )"; + RUN(script, ""); + WAIT_ON_OUTPUT("commands().length", "1\n"); + TEST( m_test->setClipboard(secretData("secret")) ); + waitFor(2 * waitMsPasteClipboard); + WAIT_ON_OUTPUT("read" << "0" << "1" << "2", "SECRET\n\n"); + RUN("count", "1\n"); } void Tests::currentClipboardOwner() diff --git a/src/tests/tests.h b/src/tests/tests.h index 89d70f9f96..b1ffb9de9a 100644 --- a/src/tests/tests.h +++ b/src/tests/tests.h @@ -306,6 +306,7 @@ private slots: void startServerAndRunCommand(); void avoidStoringPasswords(); + void automaticCommandForPasswords(); void currentClipboardOwner();