Skip to content
GitLab
Projects
Groups
Snippets
/
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
Menu
Open sidebar
Network
KDE Connect
Commits
5d970ddd
Commit
5d970ddd
authored
Dec 13, 2021
by
Méven Car
Browse files
Port clipboard plugin to KGuiaddons KSystemClipboard
parent
bfc878fa
Pipeline
#109323
failed with stage
in 2 minutes and 43 seconds
Changes
8
Pipelines
1
Hide whitespace changes
Inline
Side-by-side
CMakeLists.txt
View file @
5d970ddd
...
...
@@ -12,7 +12,7 @@ if (SAILFISHOS)
set
(
KF5_MIN_VERSION
"5.36.0"
)
set
(
QT_MIN_VERSION
"5.6.0"
)
else
()
set
(
KF5_MIN_VERSION
"5.
71
.0"
)
set
(
KF5_MIN_VERSION
"5.
89
.0"
)
set
(
QT_MIN_VERSION
"5.10.0"
)
endif
()
set
(
QCA_MIN_VERSION
"2.1.0"
)
...
...
@@ -60,7 +60,7 @@ else()
find_package
(
Qca-qt5
${
QCA_MIN_VERSION
}
REQUIRED
)
set
(
Qca_LIBRARY qca-qt5
)
set
(
KF5_REQUIRED_COMPONENTS I18n ConfigWidgets DBusAddons IconThemes Notifications KIO KCMUtils Service Solid Kirigami2 People WindowSystem
)
set
(
KF5_REQUIRED_COMPONENTS I18n ConfigWidgets DBusAddons IconThemes Notifications KIO KCMUtils Service Solid Kirigami2 People WindowSystem
GuiAddons
)
set
(
KF5_OPTIONAL_COMPONENTS DocTools Wayland
)
set_package_properties
(
KF5Kirigami2 PROPERTIES
...
...
plugins/clipboard/CMakeLists.txt
View file @
5d970ddd
if
(
UNIX AND NOT APPLE
)
find_package
(
QtWaylandScanner REQUIRED
)
find_package
(
Wayland 1.15 COMPONENTS Client
)
find_package
(
Qt5
${
QT_MIN_VERSION
}
CONFIG REQUIRED COMPONENTS WaylandClient
)
endif
()
set
(
debug_file_SRCS
)
ecm_qt_declare_logging_category
(
...
...
@@ -17,20 +12,9 @@ set(kdeconnect_clipboard_SRCS
${
debug_file_SRCS
}
)
if
(
UNIX AND NOT APPLE
)
list
(
APPEND kdeconnect_clipboard_SRCS datacontrol.cpp
)
ecm_add_qtwayland_client_protocol
(
kdeconnect_clipboard_SRCS
PROTOCOL wlr-data-control-unstable-v1.xml
BASENAME wlr-data-control-unstable-v1
)
set
(
kdeconnect_clipboard_WL_LINK_LIBS Qt5::GuiPrivate
# for native interface to get wl_seat
Wayland::Client Qt::WaylandClient
)
endif
()
kdeconnect_add_plugin
(
kdeconnect_clipboard SOURCES
${
kdeconnect_clipboard_SRCS
}
)
target_link_libraries
(
kdeconnect_clipboard kdeconnectcore
Qt
5::Gui
KF
5::Gui
Addons
${
kdeconnect_clipboard_WL_LINK_LIBS
}
)
plugins/clipboard/clipboardlistener.cpp
View file @
5d970ddd
...
...
@@ -5,15 +5,11 @@
*/
#include
"clipboardlistener.h"
#include
<QDebug>
#include
<QMimeData>
#if !defined(Q_OS_WIN) && !defined(Q_OS_MAC)
#include
"datacontrol.h"
#endif
#include
<KSystemClipboard>
ClipboardListener
::
ClipboardListener
()
{}
#include
<QMimeData>
#include
<QDateTime>
QString
ClipboardListener
::
currentContent
()
{
...
...
@@ -27,17 +23,9 @@ qint64 ClipboardListener::updateTimestamp(){
ClipboardListener
*
ClipboardListener
::
instance
()
{
static
ClipboardListener
*
me
=
nullptr
;
static
ClipboardListener
*
me
=
nullptr
;
if
(
!
me
)
{
#if !defined(Q_OS_WIN) && !defined(Q_OS_MAC)
if
(
QGuiApplication
::
platformName
().
startsWith
(
QLatin1String
(
"wayland"
),
Qt
::
CaseInsensitive
))
{
me
=
new
WaylandClipboardListener
();
}
else
{
#endif
me
=
new
QClipboardListener
();
#if !defined(Q_OS_WIN) && !defined(Q_OS_MAC)
}
#endif
me
=
new
ClipboardListener
();
}
return
me
;
}
...
...
@@ -48,23 +36,23 @@ void ClipboardListener::refreshContent(const QString& content)
m_currentContent
=
content
;
}
Q
ClipboardListener
::
Q
ClipboardListener
()
:
clipboard
(
QGuiApplication
::
clipboard
())
ClipboardListener
::
ClipboardListener
()
:
clipboard
(
KSystemClipboard
::
instance
())
{
#ifdef Q_OS_MAC
connect
(
&
m_clipboardMonitorTimer
,
&
QTimer
::
timeout
,
this
,
[
this
](){
updateClipboard
(
QClipboard
::
Clipboard
);
});
m_clipboardMonitorTimer
.
start
(
1000
);
// Refresh 1s
#endif
connect
(
clipboard
,
&
Q
Clipboard
::
changed
,
this
,
&
Q
ClipboardListener
::
updateClipboard
);
connect
(
clipboard
,
&
KSystem
Clipboard
::
changed
,
this
,
&
ClipboardListener
::
updateClipboard
);
}
void
Q
ClipboardListener
::
updateClipboard
(
QClipboard
::
Mode
mode
)
void
ClipboardListener
::
updateClipboard
(
QClipboard
::
Mode
mode
)
{
if
(
mode
!=
QClipboard
::
Clipboard
)
{
return
;
}
const
QString
content
=
clipboard
->
text
();
const
QString
content
=
clipboard
->
text
(
QClipboard
::
Clipboard
);
if
(
content
==
m_currentContent
)
{
return
;
}
...
...
@@ -72,39 +60,10 @@ void QClipboardListener::updateClipboard(QClipboard::Mode mode)
Q_EMIT
clipboardChanged
(
content
);
}
void
QClipboardListener
::
setText
(
const
QString
&
content
)
{
refreshContent
(
content
);
clipboard
->
setText
(
content
);
}
#if !defined(Q_OS_WIN) && !defined(Q_OS_MAC)
WaylandClipboardListener
::
WaylandClipboardListener
()
:
m_dataControl
(
new
DataControl
(
this
))
{
connect
(
m_dataControl
,
&
DataControl
::
changed
,
this
,
&
WaylandClipboardListener
::
refresh
);
}
void
WaylandClipboardListener
::
setText
(
const
QString
&
content
)
void
ClipboardListener
::
setText
(
const
QString
&
content
)
{
refreshContent
(
content
);
auto
mime
=
new
QMimeData
;
mime
->
setText
(
content
);
m_dataControl
->
setMimeData
(
mime
,
QClipboard
::
Clipboard
);
clipboard
->
setMimeData
(
mime
,
QClipboard
::
Clipboard
);
}
void
WaylandClipboardListener
::
refresh
()
{
const
QMimeData
*
mime
=
m_dataControl
->
mimeData
(
QClipboard
::
Clipboard
);
if
(
!
mime
||
!
mime
->
hasText
())
{
return
;
}
const
QString
content
=
mime
->
text
();
if
(
content
==
m_currentContent
)
{
return
;
}
refreshContent
(
content
);
Q_EMIT
clipboardChanged
(
content
);
}
#endif
plugins/clipboard/clipboardlistener.h
View file @
5d970ddd
...
...
@@ -7,11 +7,14 @@
#ifndef CLIPBOARDLISTENER_H
#define CLIPBOARDLISTENER_H
#include
<QDateTime>
#include
<QTimer>
#include
<QObject>
#include
<QClipboard>
#include
<QGuiApplication>
#ifdef Q_OS_MAC
#include
<QTimer>
#endif
class
KSystemClipboard
;
/**
* Wrapper around QClipboard, which emits clipboardChanged only when it really changed
...
...
@@ -32,43 +35,21 @@ private:
public:
static
ClipboardListener
*
instance
();
virtual
void
setText
(
const
QString
&
content
)
=
0
;
void
setText
(
const
QString
&
content
);
QString
currentContent
();
qint64
updateTimestamp
();
Q_SIGNALS:
void
clipboardChanged
(
const
QString
&
content
);
};
class
QClipboardListener
:
public
ClipboardListener
{
public:
QClipboardListener
();
void
setText
(
const
QString
&
content
)
override
;
private:
#ifdef Q_OS_MAC
QTimer
m_clipboardMonitorTimer
;
#endif
void
updateClipboard
(
QClipboard
::
Mode
mode
);
QClipboard
*
clipboard
;
};
class
DataControl
;
class
WaylandClipboardListener
:
public
ClipboardListener
{
public:
WaylandClipboardListener
();
void
setText
(
const
QString
&
content
)
override
;
private:
void
refresh
();
DataControl
*
m_dataControl
;
#ifdef Q_OS_MAC
QTimer
*
m_clipboardMonitorTimer
;
#endif
KSystemClipboard
*
clipboard
;
};
#endif
plugins/clipboard/clipboardplugin.cpp
View file @
5d970ddd
...
...
@@ -40,7 +40,6 @@ void ClipboardPlugin::sendConnectPacket()
sendPacket
(
np
);
}
bool
ClipboardPlugin
::
receivePacket
(
const
NetworkPacket
&
np
)
{
QString
content
=
np
.
get
<
QString
>
(
QStringLiteral
(
"content"
));
...
...
plugins/clipboard/datacontrol.cpp
deleted
100644 → 0
View file @
bfc878fa
/*
SPDX-FileCopyrightText: 2020 David Edmundson <davidedmundson@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include
"datacontrol.h"
#include
<QDebug>
#include
<QFile>
#include
<QFutureWatcher>
#include
<QGuiApplication>
#include
<QPointer>
#include
<QMimeData>
#include
<QtWaylandClient/QWaylandClientExtension>
#include
<qpa/qplatformnativeinterface.h>
#include
<errno.h>
#include
<poll.h>
#include
<string.h>
#include
<unistd.h>
#include
"qwayland-wlr-data-control-unstable-v1.h"
static
QString
utf8Text
()
{
return
QStringLiteral
(
"text/plain;charset=utf-8"
);
}
class
DataControlDeviceManager
:
public
QWaylandClientExtensionTemplate
<
DataControlDeviceManager
>
,
public
QtWayland
::
zwlr_data_control_manager_v1
{
Q_OBJECT
public:
DataControlDeviceManager
()
:
QWaylandClientExtensionTemplate
<
DataControlDeviceManager
>
(
2
)
{
}
~
DataControlDeviceManager
()
override
{
destroy
();
}
};
class
DataControlOffer
:
public
QMimeData
,
public
QtWayland
::
zwlr_data_control_offer_v1
{
Q_OBJECT
public:
DataControlOffer
(
struct
::
zwlr_data_control_offer_v1
*
id
)
:
QtWayland
::
zwlr_data_control_offer_v1
(
id
)
{
}
~
DataControlOffer
()
override
{
destroy
();
}
QStringList
formats
()
const
override
{
return
m_receivedFormats
;
}
bool
hasFormat
(
const
QString
&
mimeType
)
const
override
{
if
(
mimeType
==
QStringLiteral
(
"text/plain"
)
&&
m_receivedFormats
.
contains
(
utf8Text
()))
{
return
true
;
}
return
m_receivedFormats
.
contains
(
mimeType
);
}
protected:
void
zwlr_data_control_offer_v1_offer
(
const
QString
&
mime_type
)
override
{
m_receivedFormats
<<
mime_type
;
}
QVariant
retrieveData
(
const
QString
&
mimeType
,
QVariant
::
Type
type
)
const
override
;
private:
static
bool
readData
(
int
fd
,
QByteArray
&
data
);
QStringList
m_receivedFormats
;
};
QVariant
DataControlOffer
::
retrieveData
(
const
QString
&
mimeType
,
QVariant
::
Type
type
)
const
{
Q_UNUSED
(
type
);
QString
mime
=
mimeType
;
if
(
!
m_receivedFormats
.
contains
(
mimeType
))
{
if
(
mimeType
==
QStringLiteral
(
"text/plain"
)
&&
m_receivedFormats
.
contains
(
utf8Text
()))
{
mime
=
utf8Text
();
}
else
{
return
QVariant
();
}
}
int
pipeFds
[
2
];
if
(
pipe
(
pipeFds
)
!=
0
)
{
return
QVariant
();
}
auto
t
=
const_cast
<
DataControlOffer
*>
(
this
);
t
->
receive
(
mime
,
pipeFds
[
1
]);
close
(
pipeFds
[
1
]);
/*
* Ideally we need to introduce a non-blocking QMimeData object
* Or a non-blocking constructor to QMimeData with the mimetypes that are relevant
*
* However this isn't actually any worse than X.
*/
QPlatformNativeInterface
*
native
=
qApp
->
platformNativeInterface
();
auto
display
=
static_cast
<
struct
::
wl_display
*>
(
native
->
nativeResourceForIntegration
(
"wl_display"
));
wl_display_flush
(
display
);
QFile
readPipe
;
if
(
readPipe
.
open
(
pipeFds
[
0
],
QIODevice
::
ReadOnly
))
{
QByteArray
data
;
if
(
readData
(
pipeFds
[
0
],
data
))
{
close
(
pipeFds
[
0
]);
return
data
;
}
close
(
pipeFds
[
0
]);
}
return
QVariant
();
}
// reads data from a file descriptor with a timeout of 1 second
// true if data is read successfully
bool
DataControlOffer
::
readData
(
int
fd
,
QByteArray
&
data
)
{
pollfd
pfds
[
1
];
pfds
[
0
].
fd
=
fd
;
pfds
[
0
].
events
=
POLLIN
;
while
(
true
)
{
const
int
ready
=
poll
(
pfds
,
1
,
1000
);
if
(
ready
<
0
)
{
if
(
errno
!=
EINTR
)
{
qWarning
(
"DataControlOffer: poll() failed: %s"
,
strerror
(
errno
));
return
false
;
}
}
else
if
(
ready
==
0
)
{
qWarning
(
"DataControlOffer: timeout reading from pipe"
);
return
false
;
}
else
{
char
buf
[
4096
];
int
n
=
read
(
fd
,
buf
,
sizeof
buf
);
if
(
n
<
0
)
{
qWarning
(
"DataControlOffer: read() failed: %s"
,
strerror
(
errno
));
return
false
;
}
else
if
(
n
==
0
)
{
return
true
;
}
else
if
(
n
>
0
)
{
data
.
append
(
buf
,
n
);
}
}
}
}
class
DataControlSource
:
public
QObject
,
public
QtWayland
::
zwlr_data_control_source_v1
{
Q_OBJECT
public:
DataControlSource
(
struct
::
zwlr_data_control_source_v1
*
id
,
QMimeData
*
mimeData
);
DataControlSource
();
~
DataControlSource
()
override
{
destroy
();
}
QMimeData
*
mimeData
()
{
return
m_mimeData
;
}
Q_SIGNALS:
void
cancelled
();
protected:
void
zwlr_data_control_source_v1_send
(
const
QString
&
mime_type
,
int32_t
fd
)
override
;
void
zwlr_data_control_source_v1_cancelled
()
override
;
private:
QMimeData
*
m_mimeData
;
};
DataControlSource
::
DataControlSource
(
struct
::
zwlr_data_control_source_v1
*
id
,
QMimeData
*
mimeData
)
:
QtWayland
::
zwlr_data_control_source_v1
(
id
)
,
m_mimeData
(
mimeData
)
{
for
(
const
QString
&
format
:
mimeData
->
formats
())
{
offer
(
format
);
}
if
(
mimeData
->
hasText
())
{
// ensure GTK applications get this mimetype to avoid them discarding the offer
offer
(
QStringLiteral
(
"text/plain;charset=utf-8"
));
}
}
void
DataControlSource
::
zwlr_data_control_source_v1_send
(
const
QString
&
mime_type
,
int32_t
fd
)
{
QFile
c
;
QString
send_mime_type
=
mime_type
;
if
(
send_mime_type
==
QStringLiteral
(
"text/plain;charset=utf-8"
))
{
// if we get a request on the fallback mime, send the data from the original mime type
send_mime_type
=
QStringLiteral
(
"text/plain"
);
}
if
(
c
.
open
(
fd
,
QFile
::
WriteOnly
,
QFile
::
AutoCloseHandle
))
{
c
.
write
(
m_mimeData
->
data
(
send_mime_type
));
c
.
close
();
}
}
void
DataControlSource
::
zwlr_data_control_source_v1_cancelled
()
{
Q_EMIT
cancelled
();
}
class
DataControlDevice
:
public
QObject
,
public
QtWayland
::
zwlr_data_control_device_v1
{
Q_OBJECT
public:
DataControlDevice
(
struct
::
zwlr_data_control_device_v1
*
id
)
:
QtWayland
::
zwlr_data_control_device_v1
(
id
)
{
}
~
DataControlDevice
()
override
{
destroy
();
}
void
setSelection
(
std
::
unique_ptr
<
DataControlSource
>
selection
);
QMimeData
*
receivedSelection
()
{
return
m_receivedSelection
.
get
();
}
QMimeData
*
selection
()
{
return
m_selection
?
m_selection
->
mimeData
()
:
nullptr
;
}
void
setPrimarySelection
(
std
::
unique_ptr
<
DataControlSource
>
selection
);
QMimeData
*
receivedPrimarySelection
()
{
return
m_receivedPrimarySelection
.
get
();
}
QMimeData
*
primarySelection
()
{
return
m_primarySelection
?
m_primarySelection
->
mimeData
()
:
nullptr
;
}
Q_SIGNALS:
void
receivedSelectionChanged
();
void
selectionChanged
();
void
receivedPrimarySelectionChanged
();
void
primarySelectionChanged
();
protected:
void
zwlr_data_control_device_v1_data_offer
(
struct
::
zwlr_data_control_offer_v1
*
id
)
override
{
new
DataControlOffer
(
id
);
// this will become memory managed when we retrieve the selection event
// a compositor calling data_offer without doing that would be a bug
}
void
zwlr_data_control_device_v1_selection
(
struct
::
zwlr_data_control_offer_v1
*
id
)
override
{
if
(
!
id
)
{
m_receivedSelection
.
reset
();
}
else
{
auto
deriv
=
QtWayland
::
zwlr_data_control_offer_v1
::
fromObject
(
id
);
auto
offer
=
dynamic_cast
<
DataControlOffer
*>
(
deriv
);
// dynamic because of the dual inheritance
m_receivedSelection
.
reset
(
offer
);
}
Q_EMIT
receivedSelectionChanged
();
}
void
zwlr_data_control_device_v1_primary_selection
(
struct
::
zwlr_data_control_offer_v1
*
id
)
override
{
if
(
!
id
)
{
m_receivedPrimarySelection
.
reset
();
}
else
{
auto
deriv
=
QtWayland
::
zwlr_data_control_offer_v1
::
fromObject
(
id
);
auto
offer
=
dynamic_cast
<
DataControlOffer
*>
(
deriv
);
// dynamic because of the dual inheritance
m_receivedPrimarySelection
.
reset
(
offer
);
}
Q_EMIT
receivedPrimarySelectionChanged
();
}
private:
std
::
unique_ptr
<
DataControlSource
>
m_selection
;
// selection set locally
std
::
unique_ptr
<
DataControlOffer
>
m_receivedSelection
;
// latest selection set from externally to here
std
::
unique_ptr
<
DataControlSource
>
m_primarySelection
;
// selection set locally
std
::
unique_ptr
<
DataControlOffer
>
m_receivedPrimarySelection
;
// latest selection set from externally to here
};
void
DataControlDevice
::
setSelection
(
std
::
unique_ptr
<
DataControlSource
>
selection
)
{
m_selection
=
std
::
move
(
selection
);
connect
(
m_selection
.
get
(),
&
DataControlSource
::
cancelled
,
this
,
[
this
]()
{
m_selection
.
reset
();
Q_EMIT
selectionChanged
();
});
set_selection
(
m_selection
->
object
());
Q_EMIT
selectionChanged
();
}
void
DataControlDevice
::
setPrimarySelection
(
std
::
unique_ptr
<
DataControlSource
>
selection
)
{
m_primarySelection
=
std
::
move
(
selection
);
connect
(
m_primarySelection
.
get
(),
&
DataControlSource
::
cancelled
,
this
,
[
this
]()
{
m_primarySelection
.
reset
();
Q_EMIT
primarySelectionChanged
();
});
if
(
zwlr_data_control_device_v1_get_version
(
object
())
>=
ZWLR_DATA_CONTROL_DEVICE_V1_SET_PRIMARY_SELECTION_SINCE_VERSION
)
{
set_primary_selection
(
m_primarySelection
->
object
());
Q_EMIT
primarySelectionChanged
();
}
}
DataControl
::
DataControl
(
QObject
*
parent
)
:
QObject
(
parent
)
,
m_manager
(
new
DataControlDeviceManager
)
{
connect
(
m_manager
.
get
(),
&
DataControlDeviceManager
::
activeChanged
,
this
,
[
this
]()
{
if
(
m_manager
->
isActive
())
{
QPlatformNativeInterface
*
native
=
qApp
->
platformNativeInterface
();
if
(
!
native
)
{
return
;
}
auto
seat
=
static_cast
<
struct
::
wl_seat
*>
(
native
->
nativeResourceForIntegration
(
"wl_seat"
));
if
(
!
seat
)
{
return
;
}
m_device
.
reset
(
new
DataControlDevice
(
m_manager
->
get_data_device
(
seat
)));
connect
(
m_device
.
get
(),
&
DataControlDevice
::
receivedSelectionChanged
,
this
,
[
this
]()
{
Q_EMIT
changed
(
QClipboard
::
Clipboard
);
});
connect
(
m_device
.
get
(),
&
DataControlDevice
::
selectionChanged
,
this
,
[
this
]()
{
Q_EMIT
changed
(
QClipboard
::
Clipboard
);
});
connect
(
m_device
.
get
(),
&
DataControlDevice
::
receivedPrimarySelectionChanged
,
this
,
[
this
]()
{
Q_EMIT
changed
(
QClipboard
::
Selection
);
});
connect
(
m_device
.
get
(),
&
DataControlDevice
::
primarySelectionChanged
,
this
,
[
this
]()
{
Q_EMIT
changed
(
QClipboard
::
Selection
);
});
}
else
{
m_device
.
reset
();
}
});
}
DataControl
::~
DataControl
()
=
default
;
void
DataControl
::
setMimeData
(
QMimeData
*
mime
,
QClipboard
::
Mode
mode
)
{
if
(
!
m_device
)
{
return
;
}
auto
source
=
std
::
make_unique
<
DataControlSource
>
(
m_manager
->
create_data_source
(),
mime
);
if
(
mode
==
QClipboard
::
Clipboard
)
{
m_device
->
setSelection
(
std
::
move
(
source
));
}
else
if
(
mode
==
QClipboard
::
Selection
)
{
m_device
->
setPrimarySelection
(
std
::
move
(
source
));
}
}