fixed X11 clipboard issues, added support for size limit, added image support for webp/tiff/png/jpg

This commit is contained in:
draekko 2023-02-20 16:06:45 -05:00
parent 653e4badeb
commit c22571658d
19 changed files with 229 additions and 66 deletions

View File

@ -52,6 +52,7 @@ ServerConfig::ServerConfig(QSettings* settings, int numColumns, int numRows ,
m_IgnoreAutoConfigClient(false),
m_EnableDragAndDrop(false),
m_ClipboardSharing(true),
m_ClipboardSharingSize(defaultClipboardSharingSize()),
m_pMainWindow(mainWindow)
{
Q_ASSERT(m_pSettings);
@ -119,6 +120,7 @@ void ServerConfig::saveSettings()
settings().setValue("ignoreAutoConfigClient", ignoreAutoConfigClient());
settings().setValue("enableDragAndDrop", enableDragAndDrop());
settings().setValue("clipboardSharing", clipboardSharing());
settings().setValue("clipboardSharingSize", (int)clipboardSharingSize());
writeSettings<bool>(settings(), switchCorners(), "switchCorner");
@ -164,6 +166,8 @@ void ServerConfig::loadSettings()
setIgnoreAutoConfigClient(settings().value("ignoreAutoConfigClient").toBool());
setEnableDragAndDrop(settings().value("enableDragAndDrop", true).toBool());
setClipboardSharing(settings().value("clipboardSharing", true).toBool());
setClipboardSharingSize(settings().value("clipboardSharingSize",
(int) ServerConfig::defaultClipboardSharingSize()).toULongLong());
readSettings<bool>(settings(), switchCorners(), "switchCorner", false,
static_cast<int>(SwitchCorner::Count));
@ -412,3 +416,18 @@ void::ServerConfig::addToFirstEmptyGrid(const QString &clientName)
}
}
}
size_t ServerConfig::defaultClipboardSharingSize() {
return 100 * 1000 * 1000; // 100 MB
}
size_t ServerConfig::setClipboardSharingSize(size_t size) {
if (size) {
setClipboardSharing(true);
} else {
setClipboardSharing(false);
}
using std::swap;
swap (size, m_ClipboardSharingSize);
return size;
}

View File

@ -63,6 +63,8 @@ class ServerConfig : public BaseConfig
bool ignoreAutoConfigClient() const { return m_IgnoreAutoConfigClient; }
bool enableDragAndDrop() const { return m_EnableDragAndDrop; }
bool clipboardSharing() const { return m_ClipboardSharing; }
size_t clipboardSharingSize() const { return m_ClipboardSharingSize; }
static size_t defaultClipboardSharingSize();
void saveSettings();
void loadSettings();
@ -92,6 +94,7 @@ class ServerConfig : public BaseConfig
void setIgnoreAutoConfigClient(bool on) { m_IgnoreAutoConfigClient = on; }
void setEnableDragAndDrop(bool on) { m_EnableDragAndDrop = on; }
void setClipboardSharing(bool on) { m_ClipboardSharing = on; }
size_t setClipboardSharingSize(size_t size);
QList<bool>& switchCorners() { return m_SwitchCorners; }
std::vector<Hotkey>& hotkeys() { return m_Hotkeys; }
@ -125,6 +128,7 @@ class ServerConfig : public BaseConfig
bool m_IgnoreAutoConfigClient;
bool m_EnableDragAndDrop;
bool m_ClipboardSharing;
size_t m_ClipboardSharingSize;
MainWindow* m_pMainWindow;
};

View File

@ -59,6 +59,8 @@ ServerConfigDialog::ServerConfigDialog(QWidget* parent, ServerConfig& config, co
m_pCheckBoxEnableDragAndDrop->setChecked(serverConfig().enableDragAndDrop());
m_pCheckBoxEnableClipboard->setChecked(serverConfig().clipboardSharing());
m_pSpinBoxClipboardSizeLimit->setValue(serverConfig().clipboardSharingSize());
m_pSpinBoxClipboardSizeLimit->setEnabled(serverConfig().clipboardSharing());
for (const Hotkey& hotkey : serverConfig().hotkeys()) {
m_pListHotkeys->addItem(hotkey.text());
@ -108,6 +110,7 @@ void ServerConfigDialog::accept()
serverConfig().setIgnoreAutoConfigClient(m_pCheckBoxIgnoreAutoConfigClient->isChecked());
serverConfig().setEnableDragAndDrop(m_pCheckBoxEnableDragAndDrop->isChecked());
serverConfig().setClipboardSharing(m_pCheckBoxEnableClipboard->isChecked());
serverConfig().setClipboardSharingSize(m_pSpinBoxClipboardSizeLimit->value());
// now that the dialog has been accepted, copy the new server config to the original one,
// which is a reference to the one in MainWindow.

View File

@ -6,8 +6,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>795</width>
<height>534</height>
<width>816</width>
<height>580</height>
</rect>
</property>
<property name="windowTitle">
@ -17,7 +17,7 @@
<item>
<widget class="QTabWidget" name="m_pTabWidget">
<property name="currentIndex">
<number>0</number>
<number>2</number>
</property>
<widget class="QWidget" name="m_pTabScreens">
<attribute name="title">
@ -44,7 +44,7 @@
<string/>
</property>
<property name="pixmap">
<pixmap resource="Barrier.qrc">:/res/icons/64x64/user-trash.png</pixmap>
<pixmap>:/res/icons/64x64/user-trash.png</pixmap>
</property>
</widget>
</item>
@ -82,7 +82,7 @@
<string/>
</property>
<property name="pixmap">
<pixmap resource="Barrier.qrc">:/res/icons/64x64/video-display.png</pixmap>
<pixmap>:/res/icons/64x64/video-display.png</pixmap>
</property>
</widget>
</item>
@ -422,13 +422,27 @@ Double click on a screen to edit its settings.</string>
<string>&amp;Options</string>
</property>
<layout class="QGridLayout">
<item row="3" column="0">
<widget class="QCheckBox" name="m_pCheckBoxWin32KeepForeground">
<item row="5" column="0">
<widget class="QCheckBox" name="m_pCheckBoxEnableDragAndDrop">
<property name="text">
<string>Enable drag and drop file transfers</string>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QCheckBox" name="m_pCheckBoxIgnoreAutoConfigClient">
<property name="text">
<string>Ignore auto config clients</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QCheckBox" name="m_pCheckBoxScreenSaverSync">
<property name="enabled">
<bool>true</bool>
</property>
<property name="text">
<string>Don't take &amp;foreground window on Windows servers</string>
<string>S&amp;ynchronize screen savers</string>
</property>
</widget>
</item>
@ -442,13 +456,36 @@ Double click on a screen to edit its settings.</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QCheckBox" name="m_pCheckBoxScreenSaverSync">
<item row="3" column="0">
<widget class="QCheckBox" name="m_pCheckBoxWin32KeepForeground">
<property name="enabled">
<bool>true</bool>
</property>
<property name="text">
<string>S&amp;ynchronize screen savers</string>
<string>Don't take &amp;foreground window on Windows servers</string>
</property>
</widget>
</item>
<item row="7" column="0">
<spacer>
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>16</height>
</size>
</property>
</spacer>
</item>
<item row="6" column="0">
<widget class="QCheckBox" name="m_pCheckBoxEnableClipboard">
<property name="text">
<string>Enable clipboard sharing</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
@ -505,41 +542,23 @@ Double click on a screen to edit its settings.</string>
</item>
</layout>
</item>
<item row="7" column="0">
<spacer>
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>16</height>
</size>
</property>
</spacer>
</item>
<item row="5" column="0">
<widget class="QCheckBox" name="m_pCheckBoxEnableDragAndDrop">
<property name="text">
<string>Enable drag and drop file transfers</string>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QCheckBox" name="m_pCheckBoxIgnoreAutoConfigClient">
<property name="text">
<string>Ignore auto config clients</string>
</property>
</widget>
</item>
<item row="6" column="0">
<widget class="QCheckBox" name="m_pCheckBoxEnableClipboard">
<property name="text">
<string>Enable clipboard sharing</string>
</property>
<property name="checked">
<item row="8" column="0">
<widget class="QSpinBox" name="m_pSpinBoxClipboardSizeLimit">
<property name="enabled">
<bool>true</bool>
</property>
<property name="minimum">
<number>1000000</number>
</property>
<property name="maximum">
<number>1000000000</number>
</property>
<property name="singleStep">
<number>1000</number>
</property>
<property name="value">
<number>100000000</number>
</property>
</widget>
</item>
</layout>

View File

@ -59,6 +59,10 @@ public:
kText, //!< Text format, UTF-8, newline is LF
kHTML, //!< HTML format, HTML fragment, UTF-8, newline is LF
kBitmap, //!< Bitmap format, BMP 24/32bpp, BI_RGB
kPNG, //!< PNG format
kJpeg, //!< JPEG format
kTiff, //!< TIFF format
kWebp, //!< WEBP format
kNumFormats //!< The number of clipboard formats
};

View File

@ -68,6 +68,7 @@ static const OptionID kOptionScreenPreserveFocus = OPTION_CODE("SFOC")
static const OptionID kOptionRelativeMouseMoves = OPTION_CODE("MDLT");
static const OptionID kOptionWin32KeepForeground = OPTION_CODE("_KFW");
static const OptionID kOptionClipboardSharing = OPTION_CODE("CLPS");
static const OptionID kOptionClipboardSharingSize = OPTION_CODE("CLSZ");
//@}
//! @name Screen switch corner enumeration

View File

@ -70,7 +70,8 @@ Client::Client(IEventQueue* events, const std::string& name, const NetworkAddres
m_socket(NULL),
m_useSecureNetwork(args.m_enableCrypto),
m_args(args),
m_enableClipboard(true)
m_enableClipboard(true),
m_maximumClipboardSize(INT_MAX)
{
assert(m_socketFactory != NULL);
assert(m_screen != NULL);
@ -369,8 +370,19 @@ Client::setOptions(const OptionsList& options)
m_enableClipboard = *index;
break;
} else if (id == kOptionClipboardSharingSize) {
index++;
if (index != options.end()) {
m_maximumClipboardSize = *index;
}
}
}
if (m_enableClipboard && !m_maximumClipboardSize) {
m_enableClipboard = false;
LOG((CLOG_NOTE "clipboard sharing is disabled because the server "
"set the maximum clipboard size to 0"));
}
m_screen->setOptions(options);
}
@ -406,6 +418,12 @@ Client::sendClipboard(ClipboardID id)
// marshall the data
std::string data = clipboard.marshall();
if (data.size() >= m_maximumClipboardSize) {
LOG((CLOG_NOTE "Skipping clipboard transfer because the clipboard"
" contents exceeds the %i MB size limit set by the server",
m_maximumClipboardSize));
return;
}
// save and send data if different or not yet sent
if (!m_sentClipboard[id] || data != m_dataClipboard[id]) {
@ -660,7 +678,7 @@ Client::handleShapeChanged(const Event&, void*)
void
Client::handleClipboardGrabbed(const Event& event, void*)
{
if (!m_enableClipboard) {
if (!m_enableClipboard || (m_maximumClipboardSize == 0)) {
return;
}

View File

@ -224,4 +224,5 @@ private:
bool m_useSecureNetwork;
ClientArgs m_args;
bool m_enableClipboard;
size_t m_maximumClipboardSize;
};

View File

@ -23,6 +23,10 @@
#include "platform/XWindowsClipboardUTF8Converter.h"
#include "platform/XWindowsClipboardHTMLConverter.h"
#include "platform/XWindowsClipboardBMPConverter.h"
#include "platform/XWindowsClipboardJPGConverter.h"
#include "platform/XWindowsClipboardPNGConverter.h"
#include "platform/XWindowsClipboardTIFConverter.h"
#include "platform/XWindowsClipboardWEBPConverter.h"
#include "platform/XWindowsUtil.h"
#include "mt/Thread.h"
#include "arch/Arch.h"
@ -82,11 +86,15 @@ XWindowsClipboard::XWindowsClipboard(IXWindowsImpl* impl, Display* display,
}
// add converters, most desired first
m_converters.push_back(new XWindowsClipboardPNGConverter(m_display));
m_converters.push_back(new XWindowsClipboardWEBPConverter(m_display));
m_converters.push_back(new XWindowsClipboardJPGConverter(m_display));
m_converters.push_back(new XWindowsClipboardTIFConverter(m_display));
m_converters.push_back(new XWindowsClipboardBMPConverter(m_display));
m_converters.push_back(new XWindowsClipboardHTMLConverter(m_display,
"text/html"));
m_converters.push_back(new XWindowsClipboardHTMLConverter(m_display,
"application/x-moz-nativehtml"));
m_converters.push_back(new XWindowsClipboardBMPConverter(m_display));
m_converters.push_back(new XWindowsClipboardUTF8Converter(m_display,
"text/plain;charset=UTF-8"));
m_converters.push_back(new XWindowsClipboardUTF8Converter(m_display,
@ -545,31 +553,54 @@ XWindowsClipboard::icccmFillCache()
// available rather than checking TARGETS. i've seen clipboard
// owners that don't report all the targets they support.
target = converter->getAtom();
/*
#if 0
for (UInt32 i = 0; i < numTargets; ++i) {
if (converter->getAtom() == targets[i]) {
target = targets[i];
break;
}
}
*/
#endif
if (target == None) {
continue;
}
// get the data
Atom actualTarget;
IClipboard::EFormat format = converter->getFormat();
std::string targetData;
if (!icccmGetSelection(target, &actualTarget, &targetData)) {
LOG((CLOG_DEBUG1 " no data for target %s", XWindowsUtil::atomToString(m_display, target).c_str()));
m_added[format] = false;
continue;
}
if (actualTarget != target) {
LOG((CLOG_DEBUG1 " target %s not same as actual target %s",
XWindowsUtil::atomToString(m_display, target).c_str(),
XWindowsUtil::atomToString(m_display, actualTarget).c_str()));
m_added[format] = false;
continue;
}
if (targetData.empty()) {
m_added[format] = false;
LOG((CLOG_DEBUG1 " no targetdata for target %s (actual target %s)",
XWindowsUtil::atomToString(m_display, target).c_str(),
XWindowsUtil::atomToString(m_display, actualTarget).c_str()));
continue;
}
if (!converter->toIClipboard(targetData).empty()) {
// add to clipboard and note we've done it
IClipboard::EFormat format = converter->getFormat();
m_data[format] = converter->toIClipboard(targetData);
m_added[format] = true;
LOG((CLOG_DEBUG " added format %d for target %s (%u %s)", format, XWindowsUtil::atomToString(m_display, target).c_str(), targetData.size(), targetData.size() == 1 ? "byte" : "bytes"));
} else {
LOG((CLOG_DEBUG1 " no clipboard data for target %s", XWindowsUtil::atomToString(m_display, target).c_str()));
m_added[format] = false;
}
}
}
@ -799,17 +830,23 @@ XWindowsClipboard::motifFillCache()
// get the data (finally)
Atom actualTarget;
IClipboard::EFormat format = converter->getFormat();
std::string targetData;
if (!motifGetSelection(&motifFormat, &actualTarget, &targetData)) {
LOG((CLOG_DEBUG1 " no data for target %s", XWindowsUtil::atomToString(m_display, target).c_str()));
m_added[format] = false;
continue;
}
if (!converter->toIClipboard(targetData).empty()) {
// add to clipboard and note we've done it
IClipboard::EFormat format = converter->getFormat();
m_data[format] = converter->toIClipboard(targetData);
m_added[format] = true;
LOG((CLOG_DEBUG "added format %d for target %s", format, XWindowsUtil::atomToString(m_display, target).c_str()));
LOG((CLOG_DEBUG " added format %d for target %s (%u %s)", format, XWindowsUtil::atomToString(m_display, target).c_str(), targetData.size(), targetData.size() == 1 ? "byte" : "bytes"));
} else {
LOG((CLOG_DEBUG1 " no clipboard data for target %s", XWindowsUtil::atomToString(m_display, target).c_str()));
m_added[format] = false;
}
}
}

View File

@ -161,6 +161,10 @@ std::string XWindowsClipboardAnyBitmapConverter::fromIClipboard(const std::strin
std::string XWindowsClipboardAnyBitmapConverter::toIClipboard(const std::string& image) const
{
if (image.empty()) {
return {};
}
// convert to raw BMP data
UInt32 w, h, depth;
std::string rawBMP = doToIClipboard(image, w, h, depth);

View File

@ -16,6 +16,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "base/Log.h"
#include "platform/XWindowsClipboardBMPConverter.h"
// BMP file header structure
@ -103,6 +104,11 @@ XWindowsClipboardBMPConverter::getDataSize() const
std::string XWindowsClipboardBMPConverter::fromIClipboard(const std::string& bmp) const
{
if (bmp.size() <= 14 + 40) {
LOG((CLOG_DEBUG2 "fromIClipboard BMP, sized failed"));
return {};
}
// create BMP image
UInt8 header[14];
UInt8* dst = header;
@ -128,6 +134,8 @@ std::string XWindowsClipboardBMPConverter::toIClipboard(const std::string& bmp)
return {};
}
LOG((CLOG_DEBUG2 "Prepare BMP"));
// get offset to image data
UInt32 offset = fromLEU32(rawBMPHeader + 10);

View File

@ -61,6 +61,10 @@ std::string XWindowsClipboardHTMLConverter::fromIClipboard(const std::string& da
std::string XWindowsClipboardHTMLConverter::toIClipboard(const std::string& data) const
{
if (data.empty()) {
return {};
}
// Older Firefox [1] and possibly other applications use UTF-16 for text/html - handle both
// [1] https://bugzilla.mozilla.org/show_bug.cgi?id=1497580
if (Unicode::isUTF8(data)) {

View File

@ -61,6 +61,10 @@ std::string XWindowsClipboardTextConverter::fromIClipboard(const std::string& da
std::string XWindowsClipboardTextConverter::toIClipboard(const std::string& data) const
{
if (data.empty()) {
return {};
}
// convert to UTF-8
bool errors;
std::string utf8 = Unicode::textToUTF8(data, &errors);

View File

@ -61,5 +61,9 @@ std::string XWindowsClipboardUCS2Converter::fromIClipboard(const std::string& da
std::string XWindowsClipboardUCS2Converter::toIClipboard(const std::string& data) const
{
if (data.empty()) {
return {};
}
return Unicode::UCS2ToUTF8(data);
}

View File

@ -59,5 +59,9 @@ std::string XWindowsClipboardUTF8Converter::fromIClipboard(const std::string& da
std::string XWindowsClipboardUTF8Converter::toIClipboard(const std::string& data) const
{
if (data.empty()) {
return {};
}
return data;
}

View File

@ -1812,9 +1812,11 @@ XWindowsUtil::ErrorLock::internalHandler(Display* display, XErrorEvent* event)
}
void
XWindowsUtil::ErrorLock::ignoreHandler(Display*, XErrorEvent* e, void*)
XWindowsUtil::ErrorLock::ignoreHandler(Display* display, XErrorEvent* e, void*)
{
LOG((CLOG_DEBUG1 "ignoring X error: %d", e->error_code));
char errtxt[1024];
XGetErrorText(display, e->error_code, errtxt, 1023);
LOG((CLOG_DEBUG1 "ignoring X error: %d - %.1023s", e->error_code, errtxt));
}
void

View File

@ -744,6 +744,9 @@ Config::readSectionOptions(ConfigReadContext& s)
else if (name == "clipboardSharing") {
addOption("", kOptionClipboardSharing, s.parseBoolean(value));
}
else if (name == "clipboardSharingSize") {
addOption("", kOptionClipboardSharingSize, s.parseInt(value));
}
else {
handled = false;
@ -1360,6 +1363,9 @@ Config::getOptionName(OptionID id)
if (id == kOptionClipboardSharing) {
return "clipboardSharing";
}
if (id == kOptionClipboardSharingSize) {
return "clipboardSharingSize";
}
return NULL;
}
@ -1376,7 +1382,8 @@ std::string Config::getOptionValue(OptionID id, OptionValue value)
id == kOptionRelativeMouseMoves ||
id == kOptionWin32KeepForeground ||
id == kOptionScreenPreserveFocus ||
id == kOptionClipboardSharing) {
id == kOptionClipboardSharing ||
id == kOptionClipboardSharingSize) {
return (value != 0) ? "true" : "false";
}
if (id == kOptionModifierMapForShift ||

View File

@ -90,6 +90,7 @@ Server::Server(
m_writeToDropDirThread(NULL),
m_ignoreFileTransfer(false),
m_enableClipboard(true),
m_maximumClipboardSize(INT_MAX),
m_sendDragInfoThread(NULL),
m_waitDragInfoThread(true),
m_args(args)
@ -511,6 +512,10 @@ Server::switchScreen(BaseClientProxy* dst,
if (m_enableClipboard) {
// send the clipboard data to new active screen
for (ClipboardID id = 0; id < kClipboardEnd; ++id) {
// Hackity hackity hack
if (m_clipboards[id].m_clipboard.marshall().size() > m_maximumClipboardSize) {
continue;
}
m_active->setClipboard(id, &m_clipboards[id].m_clipboard);
}
}
@ -1177,6 +1182,15 @@ Server::processOptions()
LOG((CLOG_NOTE "clipboard sharing is disabled"));
}
}
else if (id == kOptionClipboardSharingSize) {
if (value <= 0) {
m_maximumClipboardSize = 0;
LOG((CLOG_NOTE "clipboard sharing is disabled because the "
"maximum shared clipboard size is set to 0"));
} else {
m_maximumClipboardSize = static_cast<size_t>(value);
}
}
}
if (m_relativeMoves && !newRelativeMoves) {
stopRelativeMoves();
@ -1220,7 +1234,7 @@ Server::handleShapeChanged(const Event&, void* vclient)
void
Server::handleClipboardGrabbed(const Event& event, void* vclient)
{
if (!m_enableClipboard) {
if (!m_enableClipboard || (m_maximumClipboardSize == 0)) {
return;
}
@ -1553,6 +1567,11 @@ Server::onClipboardChanged(BaseClientProxy* sender,
// ignore if data hasn't changed
std::string data = clipboard.m_clipboard.marshall();
if (data.size() > m_maximumClipboardSize) {
LOG((CLOG_NOTE "not updating clipboard because it's over the size limit (%i KB) configured by the server",
m_maximumClipboardSize));
return;
}
if (data == clipboard.m_clipboardData) {
LOG((CLOG_DEBUG "ignored screen \"%s\" update of clipboard %d (unchanged)", clipboard.m_clipboardOwner.c_str(), id));
return;

View File

@ -475,6 +475,7 @@ private:
std::string m_dragFileExt;
bool m_ignoreFileTransfer;
bool m_enableClipboard;
size_t m_maximumClipboardSize;
Thread* m_sendDragInfoThread;
bool m_waitDragInfoThread;