Add config for clipboard plugin on content types to share.
## Summary
This MR adds a config dialog for the Clipboard plugin, where the user can toggle
whether to share password and/or anything else with other devices.
Implements !39
BUG: 458063
According to previous discussion at !39 (and the bug linked), some users want to
skip sending passwords to other devices while others rely on the existing behavior.
This MR addresses that by allowing users to control those two types separately and
will replace !39 if merged.
![Screenshot with new config dialog](/uploads/3dfc6c6d69b86e6512e6a8948320a839/Screenshot_1661407807.png)
Borrowing the idea from !39, the `x-kde-passwordManagerHint` MIME data hint is used
to determine whether the content is considered secret. I've tested this method with
KeepassXC which [sets this correctly](a6d3f973fa/src/gui/Clipboard.cpp (L62)
). See test plan below.
In theory, MIME also can be used to test for images and another checkbox can
be easily added should we decided to support images later. For now though, the
enum and the config supports only passwords or "anything else". Both defaults to true,
thus keeping full backwards compatibility. I've been keeping both unchecked since
I only share in one direction (Android to Linux), which wasn't possible until this MR.
Others may want to make their own choices.
## Test Plan
Please reference the screenshot above for the steps.
From the indicator, right-click and select `Configure`. Select a device from the
left side (or pair one if needed). In "available plugins", make sure "Clipboard"
is checked, and verify that a "Configure" icon button is now shown on the right.
Clicking that icon should bring up the new config dialog, where the two check boxes
can be toggled as desired. Click on "OK" to save and apply the settings. Clicking on
"Defaults" should bring both back to checked (the default behavior).
Copy normal plain text from your favorite text editor and it should be sent to another
device if the "anything else" checkbox is checked. I've tested this on an Android phone
but I see no reason it won't work elsewhere given the code for sending has not been changed.
(Feel free to test on other devices.)
Open KeepassXC and select an entry, right click and select "Copy password". This sets
the MIME hint so it should be treated as a password, controlled by the "passwords" checkbox.
I've toggled the settings multiple times on and off. Once "OK" is clicked, the next clipboard
change should pick up the latest settings.
I've only tested on Linux desktops (Arch Linux) since I do not own a Mac. I don't believe any
OS X application sets `x-kde-passwordManagerHint` at all, so they may be controlled by "anything else".
This commit is contained in:
parent
c989be56cd
commit
b9a089344e
10 changed files with 249 additions and 9 deletions
|
@ -18,3 +18,14 @@ target_link_libraries(kdeconnect_clipboard kdeconnectcore
|
||||||
KF5::GuiAddons
|
KF5::GuiAddons
|
||||||
${kdeconnect_clipboard_WL_LINK_LIBS}
|
${kdeconnect_clipboard_WL_LINK_LIBS}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
kcoreaddons_add_plugin(kdeconnect_clipboard_config INSTALL_NAMESPACE "kdeconnect/kcms")
|
||||||
|
|
||||||
|
ki18n_wrap_ui(kdeconnect_clipboard_config clipboard_config.ui)
|
||||||
|
target_sources(kdeconnect_clipboard_config PRIVATE clipboard_config.cpp ${debug_file_SRCS})
|
||||||
|
target_link_libraries( kdeconnect_clipboard_config
|
||||||
|
kdeconnectcore
|
||||||
|
kdeconnectpluginkcm
|
||||||
|
KF5::I18n
|
||||||
|
KF5::KCMUtils
|
||||||
|
)
|
||||||
|
|
56
plugins/clipboard/clipboard_config.cpp
Normal file
56
plugins/clipboard/clipboard_config.cpp
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
/**
|
||||||
|
* SPDX-FileCopyrightText: 2022 Yuchen Shi <bolshaya_schists@mail.gravitide.co>
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "clipboard_config.h"
|
||||||
|
#include "ui_clipboard_config.h"
|
||||||
|
|
||||||
|
#include <KPluginFactory>
|
||||||
|
|
||||||
|
K_PLUGIN_FACTORY(ClipboardConfigFactory, registerPlugin<ClipboardConfig>();)
|
||||||
|
|
||||||
|
ClipboardConfig::ClipboardConfig(QWidget* parent, const QVariantList &args)
|
||||||
|
: KdeConnectPluginKcm(parent, args, QStringLiteral("kdeconnect_clipboard"))
|
||||||
|
, m_ui(new Ui::ClipboardConfigUi())
|
||||||
|
{
|
||||||
|
m_ui->setupUi(this);
|
||||||
|
|
||||||
|
connect(m_ui->check_unknown, SIGNAL(toggled(bool)), this, SLOT(changed()));
|
||||||
|
connect(m_ui->check_password, SIGNAL(toggled(bool)), this, SLOT(changed()));
|
||||||
|
}
|
||||||
|
|
||||||
|
ClipboardConfig::~ClipboardConfig()
|
||||||
|
{
|
||||||
|
delete m_ui;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ClipboardConfig::defaults()
|
||||||
|
{
|
||||||
|
KCModule::defaults();
|
||||||
|
m_ui->check_unknown->setChecked(true);
|
||||||
|
m_ui->check_password->setChecked(true);
|
||||||
|
Q_EMIT changed(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ClipboardConfig::load()
|
||||||
|
{
|
||||||
|
KCModule::load();
|
||||||
|
bool unknown = config()->getBool(QStringLiteral("sendUnknown"), true);
|
||||||
|
bool password = config()->getBool(QStringLiteral("sendPassword"), true);
|
||||||
|
m_ui->check_unknown->setChecked(unknown);
|
||||||
|
m_ui->check_password->setChecked(password);
|
||||||
|
|
||||||
|
Q_EMIT changed(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ClipboardConfig::save()
|
||||||
|
{
|
||||||
|
config()->set(QStringLiteral("sendUnknown"), m_ui->check_unknown->isChecked());
|
||||||
|
config()->set(QStringLiteral("sendPassword"), m_ui->check_password->isChecked());
|
||||||
|
KCModule::save();
|
||||||
|
Q_EMIT changed(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
#include "clipboard_config.moc"
|
34
plugins/clipboard/clipboard_config.h
Normal file
34
plugins/clipboard/clipboard_config.h
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
/**
|
||||||
|
* SPDX-FileCopyrightText: 2022 Yuchen Shi <bolshaya_schists@mail.gravitide.co>
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef CLIPBOARD_CONFIG_H
|
||||||
|
#define CLIPBOARD_CONFIG_H
|
||||||
|
|
||||||
|
#include "kcmplugin/kdeconnectpluginkcm.h"
|
||||||
|
|
||||||
|
namespace Ui {
|
||||||
|
class ClipboardConfigUi;
|
||||||
|
}
|
||||||
|
|
||||||
|
class ClipboardConfig
|
||||||
|
: public KdeConnectPluginKcm
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
ClipboardConfig(QWidget *parent, const QVariantList&);
|
||||||
|
~ClipboardConfig() override;
|
||||||
|
|
||||||
|
public Q_SLOTS:
|
||||||
|
void save() override;
|
||||||
|
void load() override;
|
||||||
|
void defaults() override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
Ui::ClipboardConfigUi *m_ui;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
69
plugins/clipboard/clipboard_config.ui
Normal file
69
plugins/clipboard/clipboard_config.ui
Normal file
|
@ -0,0 +1,69 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<ui version="4.0">
|
||||||
|
<class>ClipboardConfigUi</class>
|
||||||
|
<widget class="QWidget" name="ClipboardConfigUi">
|
||||||
|
<property name="windowModality">
|
||||||
|
<enum>Qt::WindowModal</enum>
|
||||||
|
</property>
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>0</x>
|
||||||
|
<y>0</y>
|
||||||
|
<width>368</width>
|
||||||
|
<height>241</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<property name="windowTitle">
|
||||||
|
<string>Clipboard plugin</string>
|
||||||
|
</property>
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout_1">
|
||||||
|
<property name="spacing">
|
||||||
|
<number>20</number>
|
||||||
|
</property>
|
||||||
|
<item>
|
||||||
|
<widget class="QGroupBox" name="groupBox_1">
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
|
||||||
|
<horstretch>0</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
<property name="title">
|
||||||
|
<string>Contents shared to other devices</string>
|
||||||
|
</property>
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||||
|
<item>
|
||||||
|
<widget class="QCheckBox" name="check_password">
|
||||||
|
<property name="text">
|
||||||
|
<string>Passwords (as marked by password managers)</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QCheckBox" name="check_unknown">
|
||||||
|
<property name="text">
|
||||||
|
<string>Anything else</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<spacer name="verticalSpacer">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Vertical</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeHint" stdset="0">
|
||||||
|
<size>
|
||||||
|
<width>20</width>
|
||||||
|
<height>40</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</spacer>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
<resources/>
|
||||||
|
<connections/>
|
||||||
|
</ui>
|
|
@ -16,6 +16,11 @@ QString ClipboardListener::currentContent()
|
||||||
return m_currentContent;
|
return m_currentContent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ClipboardListener::ClipboardContentType ClipboardListener::currentContentType()
|
||||||
|
{
|
||||||
|
return m_currentContentType;
|
||||||
|
}
|
||||||
|
|
||||||
qint64 ClipboardListener::updateTimestamp()
|
qint64 ClipboardListener::updateTimestamp()
|
||||||
{
|
{
|
||||||
return m_updateTimestamp;
|
return m_updateTimestamp;
|
||||||
|
@ -30,10 +35,11 @@ ClipboardListener *ClipboardListener::instance()
|
||||||
return me;
|
return me;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ClipboardListener::refreshContent(const QString &content)
|
void ClipboardListener::refreshContent(const QString &content, ClipboardListener::ClipboardContentType contentType)
|
||||||
{
|
{
|
||||||
m_updateTimestamp = QDateTime::currentDateTime().toMSecsSinceEpoch();
|
m_updateTimestamp = QDateTime::currentDateTime().toMSecsSinceEpoch();
|
||||||
m_currentContent = content;
|
m_currentContent = content;
|
||||||
|
m_currentContentType = contentType;
|
||||||
}
|
}
|
||||||
|
|
||||||
ClipboardListener::ClipboardListener()
|
ClipboardListener::ClipboardListener()
|
||||||
|
@ -54,17 +60,22 @@ void ClipboardListener::updateClipboard(QClipboard::Mode mode)
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ClipboardListener::ClipboardContentType contentType = ClipboardListener::ClipboardContentTypeUnknown;
|
||||||
|
if (clipboard->mimeData(mode)->data(QStringLiteral("x-kde-passwordManagerHint")) == QByteArrayLiteral("secret")) {
|
||||||
|
contentType = ClipboardListener::ClipboardContentTypePassword;
|
||||||
|
}
|
||||||
|
|
||||||
const QString content = clipboard->text(QClipboard::Clipboard);
|
const QString content = clipboard->text(QClipboard::Clipboard);
|
||||||
if (content == m_currentContent) {
|
if (content == m_currentContent && contentType == m_currentContentType) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
refreshContent(content);
|
refreshContent(content, contentType);
|
||||||
Q_EMIT clipboardChanged(content);
|
Q_EMIT clipboardChanged(content, contentType);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ClipboardListener::setText(const QString &content)
|
void ClipboardListener::setText(const QString &content)
|
||||||
{
|
{
|
||||||
refreshContent(content);
|
refreshContent(content, ClipboardListener::ClipboardContentTypeUnknown);
|
||||||
auto mime = new QMimeData;
|
auto mime = new QMimeData;
|
||||||
mime->setText(content);
|
mime->setText(content);
|
||||||
clipboard->setMimeData(mime, QClipboard::Clipboard);
|
clipboard->setMimeData(mime, QClipboard::Clipboard);
|
||||||
|
|
|
@ -24,10 +24,14 @@ class ClipboardListener : public QObject
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
enum ClipboardContentType { ClipboardContentTypeUnknown = 0, ClipboardContentTypePassword = 1 };
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
ClipboardListener();
|
ClipboardListener();
|
||||||
void refreshContent(const QString &content);
|
void refreshContent(const QString &content, ClipboardContentType contentType);
|
||||||
QString m_currentContent;
|
QString m_currentContent;
|
||||||
|
ClipboardContentType m_currentContentType;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
qint64 m_updateTimestamp = 0;
|
qint64 m_updateTimestamp = 0;
|
||||||
|
@ -38,10 +42,11 @@ public:
|
||||||
void setText(const QString &content);
|
void setText(const QString &content);
|
||||||
|
|
||||||
QString currentContent();
|
QString currentContent();
|
||||||
|
ClipboardContentType currentContentType();
|
||||||
qint64 updateTimestamp();
|
qint64 updateTimestamp();
|
||||||
|
|
||||||
Q_SIGNALS:
|
Q_SIGNALS:
|
||||||
void clipboardChanged(const QString &content);
|
void clipboardChanged(const QString &content, ClipboardContentType contentType);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void updateClipboard(QClipboard::Mode mode);
|
void updateClipboard(QClipboard::Mode mode);
|
||||||
|
|
|
@ -24,8 +24,20 @@ void ClipboardPlugin::connected()
|
||||||
sendConnectPacket();
|
sendConnectPacket();
|
||||||
}
|
}
|
||||||
|
|
||||||
void ClipboardPlugin::propagateClipboard(const QString &content)
|
void ClipboardPlugin::propagateClipboard(const QString &content, ClipboardListener::ClipboardContentType contentType)
|
||||||
{
|
{
|
||||||
|
if (contentType == ClipboardListener::ClipboardContentTypeUnknown) {
|
||||||
|
if (!config()->getBool(QStringLiteral("sendUnknown"), true)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else if (contentType == ClipboardListener::ClipboardContentTypePassword) {
|
||||||
|
if (!config()->getBool(QStringLiteral("sendPassword"), true)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
NetworkPacket np(PACKET_TYPE_CLIPBOARD, {{QStringLiteral("content"), content}});
|
NetworkPacket np(PACKET_TYPE_CLIPBOARD, {{QStringLiteral("content"), content}});
|
||||||
sendPacket(np);
|
sendPacket(np);
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,8 @@
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
#include <core/kdeconnectplugin.h>
|
#include <core/kdeconnectplugin.h>
|
||||||
|
|
||||||
|
#include "clipboardlistener.h"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Packet containing just clipboard contents, sent when a device updates its clipboard.
|
* Packet containing just clipboard contents, sent when a device updates its clipboard.
|
||||||
* <p>
|
* <p>
|
||||||
|
@ -46,7 +48,7 @@ public:
|
||||||
bool receivePacket(const NetworkPacket &np) override;
|
bool receivePacket(const NetworkPacket &np) override;
|
||||||
void connected() override;
|
void connected() override;
|
||||||
private Q_SLOTS:
|
private Q_SLOTS:
|
||||||
void propagateClipboard(const QString &content);
|
void propagateClipboard(const QString &content, ClipboardListener::ClipboardContentType contentType);
|
||||||
void sendConnectPacket();
|
void sendConnectPacket();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -150,6 +150,7 @@
|
||||||
"Version": "0.1",
|
"Version": "0.1",
|
||||||
"Website": "https://albertvaka.wordpress.com"
|
"Website": "https://albertvaka.wordpress.com"
|
||||||
},
|
},
|
||||||
|
"X-KDE-ConfigModule": "kdeconnect/kcms/kdeconnect_clipboard_config",
|
||||||
"X-KdeConnect-OutgoingPacketType": [
|
"X-KdeConnect-OutgoingPacketType": [
|
||||||
"kdeconnect.clipboard",
|
"kdeconnect.clipboard",
|
||||||
"kdeconnect.clipboard.connect"
|
"kdeconnect.clipboard.connect"
|
||||||
|
|
39
plugins/clipboard/kdeconnect_clipboard_config.qml
Normal file
39
plugins/clipboard/kdeconnect_clipboard_config.qml
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
/**
|
||||||
|
* SPDX-FileCopyrightText: 2022 Yuchen Shi <bolshaya_schists@mail.gravitide.co>
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||||
|
*/
|
||||||
|
|
||||||
|
import QtQuick 2.2
|
||||||
|
import QtQuick.Controls 2.2
|
||||||
|
import QtQuick.Layouts 1.1
|
||||||
|
import org.kde.kirigami 2.5 as Kirigami
|
||||||
|
import org.kde.kdeconnect 1.0
|
||||||
|
|
||||||
|
Kirigami.FormLayout {
|
||||||
|
|
||||||
|
property string device
|
||||||
|
|
||||||
|
KdeConnectPluginConfig {
|
||||||
|
id: config
|
||||||
|
deviceId: device
|
||||||
|
pluginName: "kdeconnect_clipboard"
|
||||||
|
}
|
||||||
|
|
||||||
|
Component.onCompleted: {
|
||||||
|
unknown.checked = config.get("sendUnknown", true)
|
||||||
|
password.checked = config.get("sendPassword", true)
|
||||||
|
}
|
||||||
|
|
||||||
|
CheckBox {
|
||||||
|
id: password
|
||||||
|
text: i18n("Passwords (as marked by password managers)")
|
||||||
|
onClicked: config.set("sendPassword", checked)
|
||||||
|
}
|
||||||
|
|
||||||
|
CheckBox {
|
||||||
|
id: unknown
|
||||||
|
text: i18n("Anything else")
|
||||||
|
onClicked: config.set("sendUnknown", checked)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue