chore: remove unused wl-clipboard support
This commit is contained in:
parent
16cd4695cc
commit
df3746777b
18 changed files with 0 additions and 1281 deletions
|
|
@ -77,7 +77,6 @@ This section contains general options it will begin with `[core]`
|
||||||
| computerName | string | Name used to identify the computer [default: machine's hostname] |
|
| computerName | string | Name used to identify the computer [default: machine's hostname] |
|
||||||
| useHooks | `true` or `false` | If Windows uses hooks or not [default: true] |
|
| useHooks | `true` or `false` | If Windows uses hooks or not [default: true] |
|
||||||
| language | 639 language | The language to display the GUI in [default: en] |
|
| language | 639 language | The language to display the GUI in [default: en] |
|
||||||
| wlClipboard | `true` or `false` | When true the wl-clipboard backend will be enabled [default: false] |
|
|
||||||
| enableEnterCommand | `true` or `false` | Should the enter command be triggered when the screen is entered [defaut: false] |
|
| enableEnterCommand | `true` or `false` | Should the enter command be triggered when the screen is entered [defaut: false] |
|
||||||
| enterCommand | command | A command to run when the screen is entered. |
|
| enterCommand | command | A command to run when the screen is entered. |
|
||||||
| enableExitCommand | `true` or `false` | Should the exit command be triggered when the screen is exited [defaut: false] |
|
| enableExitCommand | `true` or `false` | Should the exit command be triggered when the screen is exited [defaut: false] |
|
||||||
|
|
|
||||||
|
|
@ -56,7 +56,6 @@ public:
|
||||||
inline static const auto Display = QStringLiteral("core/display");
|
inline static const auto Display = QStringLiteral("core/display");
|
||||||
inline static const auto UseHooks = QStringLiteral("core/useHooks");
|
inline static const auto UseHooks = QStringLiteral("core/useHooks");
|
||||||
inline static const auto Language = QStringLiteral("core/language");
|
inline static const auto Language = QStringLiteral("core/language");
|
||||||
inline static const auto UseWlClipboard = QStringLiteral("core/wlClipboard");
|
|
||||||
inline static const auto EnableEnterCommand = QStringLiteral("core/enableEnterCommand");
|
inline static const auto EnableEnterCommand = QStringLiteral("core/enableEnterCommand");
|
||||||
inline static const auto ScreenEnterCommand = QStringLiteral("core/enterCommand");
|
inline static const auto ScreenEnterCommand = QStringLiteral("core/enterCommand");
|
||||||
inline static const auto EnableExitCommand = QStringLiteral("core/enableExitCommand");
|
inline static const auto EnableExitCommand = QStringLiteral("core/enableExitCommand");
|
||||||
|
|
@ -226,7 +225,6 @@ private:
|
||||||
, Settings::Core::ComputerName
|
, Settings::Core::ComputerName
|
||||||
, Settings::Core::Display
|
, Settings::Core::Display
|
||||||
, Settings::Core::UseHooks
|
, Settings::Core::UseHooks
|
||||||
, Settings::Core::UseWlClipboard
|
|
||||||
, Settings::Core::Language
|
, Settings::Core::Language
|
||||||
, Settings::Daemon::ConfigFile
|
, Settings::Daemon::ConfigFile
|
||||||
, Settings::Daemon::Elevate
|
, Settings::Daemon::Elevate
|
||||||
|
|
@ -280,7 +278,6 @@ private:
|
||||||
, Settings::Gui::ShowVersionInTitle
|
, Settings::Gui::ShowVersionInTitle
|
||||||
, Settings::Gui::IgnoreMissingKeyboardLayouts
|
, Settings::Gui::IgnoreMissingKeyboardLayouts
|
||||||
, Settings::Core::PreventSleep
|
, Settings::Core::PreventSleep
|
||||||
, Settings::Core::UseWlClipboard
|
|
||||||
, Settings::Core::EnableEnterCommand
|
, Settings::Core::EnableEnterCommand
|
||||||
, Settings::Core::EnableExitCommand
|
, Settings::Core::EnableExitCommand
|
||||||
, Settings::Client::DynamicConnectionRetry
|
, Settings::Client::DynamicConnectionRetry
|
||||||
|
|
|
||||||
|
|
@ -130,7 +130,6 @@ void SettingsDialog::initConnections() const
|
||||||
connect(ui->cbElevateDaemon, &QCheckBox::toggled, this, &SettingsDialog::setButtonBoxEnabledButtons);
|
connect(ui->cbElevateDaemon, &QCheckBox::toggled, this, &SettingsDialog::setButtonBoxEnabledButtons);
|
||||||
connect(ui->cbAutoUpdate, &QCheckBox::toggled, this, &SettingsDialog::setButtonBoxEnabledButtons);
|
connect(ui->cbAutoUpdate, &QCheckBox::toggled, this, &SettingsDialog::setButtonBoxEnabledButtons);
|
||||||
connect(ui->cbGuiDebug, &QCheckBox::toggled, this, &SettingsDialog::setButtonBoxEnabledButtons);
|
connect(ui->cbGuiDebug, &QCheckBox::toggled, this, &SettingsDialog::setButtonBoxEnabledButtons);
|
||||||
connect(ui->cbUseWlClipboard, &QCheckBox::toggled, this, &SettingsDialog::setButtonBoxEnabledButtons);
|
|
||||||
connect(ui->cbShowVersion, &QCheckBox::toggled, this, &SettingsDialog::setButtonBoxEnabledButtons);
|
connect(ui->cbShowVersion, &QCheckBox::toggled, this, &SettingsDialog::setButtonBoxEnabledButtons);
|
||||||
connect(ui->cbRequireClientCert, &QCheckBox::toggled, this, &SettingsDialog::setButtonBoxEnabledButtons);
|
connect(ui->cbRequireClientCert, &QCheckBox::toggled, this, &SettingsDialog::setButtonBoxEnabledButtons);
|
||||||
connect(ui->groupLogToFile, &QGroupBox::toggled, this, &SettingsDialog::setButtonBoxEnabledButtons);
|
connect(ui->groupLogToFile, &QGroupBox::toggled, this, &SettingsDialog::setButtonBoxEnabledButtons);
|
||||||
|
|
@ -242,7 +241,6 @@ void SettingsDialog::accept()
|
||||||
Settings::setValue(Settings::Security::CheckPeers, ui->cbRequireClientCert->isChecked());
|
Settings::setValue(Settings::Security::CheckPeers, ui->cbRequireClientCert->isChecked());
|
||||||
Settings::setValue(Settings::Core::Language, I18N::nativeTo639Name(ui->comboLanguage->currentText()));
|
Settings::setValue(Settings::Core::Language, I18N::nativeTo639Name(ui->comboLanguage->currentText()));
|
||||||
Settings::setValue(Settings::Log::GuiDebug, ui->cbGuiDebug->isChecked());
|
Settings::setValue(Settings::Log::GuiDebug, ui->cbGuiDebug->isChecked());
|
||||||
Settings::setValue(Settings::Core::UseWlClipboard, ui->cbUseWlClipboard->isChecked());
|
|
||||||
Settings::setValue(Settings::Gui::ShowVersionInTitle, ui->cbShowVersion->isChecked());
|
Settings::setValue(Settings::Gui::ShowVersionInTitle, ui->cbShowVersion->isChecked());
|
||||||
Settings::setValue(Settings::Core::EnableEnterCommand, ui->cbRunEnterCommand->isChecked());
|
Settings::setValue(Settings::Core::EnableEnterCommand, ui->cbRunEnterCommand->isChecked());
|
||||||
Settings::setValue(Settings::Core::EnableExitCommand, ui->cbRunExitCommand->isChecked());
|
Settings::setValue(Settings::Core::EnableExitCommand, ui->cbRunExitCommand->isChecked());
|
||||||
|
|
@ -271,7 +269,6 @@ void SettingsDialog::loadFromConfig()
|
||||||
ui->cbElevateDaemon->setChecked(Settings::value(Settings::Daemon::Elevate).toBool());
|
ui->cbElevateDaemon->setChecked(Settings::value(Settings::Daemon::Elevate).toBool());
|
||||||
ui->cbAutoUpdate->setChecked(Settings::value(Settings::Gui::AutoUpdateCheck).toBool());
|
ui->cbAutoUpdate->setChecked(Settings::value(Settings::Gui::AutoUpdateCheck).toBool());
|
||||||
ui->cbGuiDebug->setChecked(Settings::value(Settings::Log::GuiDebug).toBool());
|
ui->cbGuiDebug->setChecked(Settings::value(Settings::Log::GuiDebug).toBool());
|
||||||
ui->cbUseWlClipboard->setChecked(Settings::value(Settings::Core::UseWlClipboard).toBool());
|
|
||||||
ui->cbShowVersion->setChecked(Settings::value(Settings::Gui::ShowVersionInTitle).toBool());
|
ui->cbShowVersion->setChecked(Settings::value(Settings::Gui::ShowVersionInTitle).toBool());
|
||||||
ui->cbRunEnterCommand->setChecked(Settings::value(Settings::Core::EnableEnterCommand).toBool());
|
ui->cbRunEnterCommand->setChecked(Settings::value(Settings::Core::EnableEnterCommand).toBool());
|
||||||
ui->cbRunExitCommand->setChecked(Settings::value(Settings::Core::EnableExitCommand).toBool());
|
ui->cbRunExitCommand->setChecked(Settings::value(Settings::Core::EnableExitCommand).toBool());
|
||||||
|
|
@ -402,14 +399,6 @@ void SettingsDialog::updateControls()
|
||||||
ui->groupService->setVisible(false);
|
ui->groupService->setVisible(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
// wl-clipboard support only works on wayland.
|
|
||||||
// options should only be available when we are *not* running on wayland.
|
|
||||||
if (deskflow::platform::isWayland()) {
|
|
||||||
ui->cbUseWlClipboard->setEnabled(writable);
|
|
||||||
} else if (ui->widgetWlClipboard->isVisibleTo(ui->tabAdvanced)) {
|
|
||||||
ui->widgetWlClipboard->setVisible(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
ui->widgetLogFilename->setEnabled(writable && logToFile);
|
ui->widgetLogFilename->setEnabled(writable && logToFile);
|
||||||
|
|
||||||
updateTlsControls();
|
updateTlsControls();
|
||||||
|
|
@ -443,7 +432,6 @@ bool SettingsDialog::isModified() const
|
||||||
(ui->cbElevateDaemon->isChecked() != Settings::value(Settings::Daemon::Elevate).toBool()) ||
|
(ui->cbElevateDaemon->isChecked() != Settings::value(Settings::Daemon::Elevate).toBool()) ||
|
||||||
(ui->cbAutoUpdate->isChecked() != Settings::value(Settings::Gui::AutoUpdateCheck).toBool()) ||
|
(ui->cbAutoUpdate->isChecked() != Settings::value(Settings::Gui::AutoUpdateCheck).toBool()) ||
|
||||||
(ui->cbGuiDebug->isChecked() != Settings::value(Settings::Log::GuiDebug).toBool()) ||
|
(ui->cbGuiDebug->isChecked() != Settings::value(Settings::Log::GuiDebug).toBool()) ||
|
||||||
(ui->cbUseWlClipboard->isChecked() != Settings::value(Settings::Core::UseWlClipboard).toBool()) ||
|
|
||||||
(ui->cbShowVersion->isChecked() != Settings::value(Settings::Gui::ShowVersionInTitle).toBool()) ||
|
(ui->cbShowVersion->isChecked() != Settings::value(Settings::Gui::ShowVersionInTitle).toBool()) ||
|
||||||
(ui->rbIconMono->isChecked() != Settings::value(Settings::Gui::SymbolicTrayIcon).toBool()) ||
|
(ui->rbIconMono->isChecked() != Settings::value(Settings::Gui::SymbolicTrayIcon).toBool()) ||
|
||||||
(ui->groupService->isChecked() != (processMode == Settings::ProcessMode::Service)) ||
|
(ui->groupService->isChecked() != (processMode == Settings::ProcessMode::Service)) ||
|
||||||
|
|
@ -479,7 +467,6 @@ bool SettingsDialog::isDefault() const
|
||||||
(ui->cbElevateDaemon->isChecked() == Settings::defaultValue(Settings::Daemon::Elevate).toBool()) &&
|
(ui->cbElevateDaemon->isChecked() == Settings::defaultValue(Settings::Daemon::Elevate).toBool()) &&
|
||||||
(ui->cbAutoUpdate->isChecked() == Settings::defaultValue(Settings::Gui::AutoUpdateCheck).toBool()) &&
|
(ui->cbAutoUpdate->isChecked() == Settings::defaultValue(Settings::Gui::AutoUpdateCheck).toBool()) &&
|
||||||
(ui->cbGuiDebug->isChecked() == Settings::defaultValue(Settings::Log::GuiDebug).toBool()) &&
|
(ui->cbGuiDebug->isChecked() == Settings::defaultValue(Settings::Log::GuiDebug).toBool()) &&
|
||||||
(ui->cbUseWlClipboard->isChecked() == Settings::defaultValue(Settings::Core::UseWlClipboard).toBool()) &&
|
|
||||||
(ui->cbShowVersion->isChecked() == Settings::defaultValue(Settings::Gui::ShowVersionInTitle).toBool()) &&
|
(ui->cbShowVersion->isChecked() == Settings::defaultValue(Settings::Gui::ShowVersionInTitle).toBool()) &&
|
||||||
(ui->rbIconMono->isChecked() == Settings::defaultValue(Settings::Gui::SymbolicTrayIcon).toBool()) &&
|
(ui->rbIconMono->isChecked() == Settings::defaultValue(Settings::Gui::SymbolicTrayIcon).toBool()) &&
|
||||||
(ui->groupService->isChecked() == (processMode == Settings::ProcessMode::Service)) &&
|
(ui->groupService->isChecked() == (processMode == Settings::ProcessMode::Service)) &&
|
||||||
|
|
@ -508,7 +495,6 @@ void SettingsDialog::resetToDefault()
|
||||||
ui->cbElevateDaemon->setChecked(Settings::defaultValue(Settings::Daemon::Elevate).toBool());
|
ui->cbElevateDaemon->setChecked(Settings::defaultValue(Settings::Daemon::Elevate).toBool());
|
||||||
ui->cbAutoUpdate->setChecked(Settings::defaultValue(Settings::Gui::AutoUpdateCheck).toBool());
|
ui->cbAutoUpdate->setChecked(Settings::defaultValue(Settings::Gui::AutoUpdateCheck).toBool());
|
||||||
ui->cbGuiDebug->setChecked(Settings::defaultValue(Settings::Log::GuiDebug).toBool());
|
ui->cbGuiDebug->setChecked(Settings::defaultValue(Settings::Log::GuiDebug).toBool());
|
||||||
ui->cbUseWlClipboard->setChecked(Settings::defaultValue(Settings::Core::UseWlClipboard).toBool());
|
|
||||||
ui->cbShowVersion->setChecked(Settings::defaultValue(Settings::Gui::ShowVersionInTitle).toBool());
|
ui->cbShowVersion->setChecked(Settings::defaultValue(Settings::Gui::ShowVersionInTitle).toBool());
|
||||||
ui->cbRunEnterCommand->setChecked(Settings::defaultValue(Settings::Core::EnableEnterCommand).toBool());
|
ui->cbRunEnterCommand->setChecked(Settings::defaultValue(Settings::Core::EnableEnterCommand).toBool());
|
||||||
ui->cbRunExitCommand->setChecked(Settings::defaultValue(Settings::Core::EnableExitCommand).toBool());
|
ui->cbRunExitCommand->setChecked(Settings::defaultValue(Settings::Core::EnableExitCommand).toBool());
|
||||||
|
|
|
||||||
|
|
@ -807,29 +807,6 @@
|
||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
|
||||||
<widget class="QWidget" name="widgetWlClipboard" native="true">
|
|
||||||
<layout class="QVBoxLayout" name="verticalLayout_5">
|
|
||||||
<item>
|
|
||||||
<widget class="QCheckBox" name="cbUseWlClipboard">
|
|
||||||
<property name="text">
|
|
||||||
<string>Enable wl-clipboard support</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<widget class="QLabel" name="lblWlClipboard">
|
|
||||||
<property name="text">
|
|
||||||
<string><html><head/><body><p>Requires the wl-clipboard package</p><p>When using wl-clipboard v2.2.1, there is a focus stealing bug that may make Deskflow harder to use. This has been fixed when using the wl-clipboard master branch, unless your Compositor lacks wlroots-data-control protocol support.</p></body></html></string>
|
|
||||||
</property>
|
|
||||||
<property name="wordWrap">
|
|
||||||
<bool>true</bool>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
</layout>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item>
|
<item>
|
||||||
<layout class="QFormLayout" name="formLayout_3">
|
<layout class="QFormLayout" name="formLayout_3">
|
||||||
<property name="fieldGrowthPolicy">
|
<property name="fieldGrowthPolicy">
|
||||||
|
|
|
||||||
|
|
@ -151,10 +151,6 @@ elseif(UNIX)
|
||||||
EiKeyState.h
|
EiKeyState.h
|
||||||
EiScreen.cpp
|
EiScreen.cpp
|
||||||
EiScreen.h
|
EiScreen.h
|
||||||
WlClipboardCollection.cpp
|
|
||||||
WlClipboardCollection.h
|
|
||||||
WlClipboard.cpp
|
|
||||||
WlClipboard.h
|
|
||||||
)
|
)
|
||||||
# The Portal sources also require EI.
|
# The Portal sources also require EI.
|
||||||
if(LIBPORTAL_FOUND)
|
if(LIBPORTAL_FOUND)
|
||||||
|
|
|
||||||
|
|
@ -1,411 +0,0 @@
|
||||||
/*
|
|
||||||
* Deskflow -- mouse and keyboard sharing utility
|
|
||||||
* SPDX-FileCopyrightText: (C) 2025 Deskflow Developers
|
|
||||||
* SPDX-License-Identifier: GPL-2.0-only WITH LicenseRef-OpenSSL-Exception
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "platform/WlClipboard.h"
|
|
||||||
|
|
||||||
#include "base/Log.h"
|
|
||||||
|
|
||||||
#include <chrono>
|
|
||||||
#include <common/Settings.h>
|
|
||||||
#include <fcntl.h>
|
|
||||||
#include <poll.h>
|
|
||||||
#include <spawn.h>
|
|
||||||
#include <sys/wait.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
|
|
||||||
#include <QDateTime>
|
|
||||||
#include <QProcess>
|
|
||||||
#include <QStandardPaths>
|
|
||||||
|
|
||||||
namespace {
|
|
||||||
|
|
||||||
inline static const auto s_copyApp = QStringLiteral("wl-copy");
|
|
||||||
inline static const auto s_pasteApp = QStringLiteral("wl-paste");
|
|
||||||
|
|
||||||
// wl-clipboard args
|
|
||||||
inline static const auto s_listTypes = QStringLiteral("--list-types");
|
|
||||||
inline static const auto s_isPrimary = QStringLiteral("--primary");
|
|
||||||
inline static const auto s_noNewLine = QStringLiteral("-n");
|
|
||||||
inline static const auto s_readType = QStringLiteral("-t%1");
|
|
||||||
|
|
||||||
// MIME types for different clipboard formats
|
|
||||||
inline static const auto s_mimeTypeText = QStringLiteral("text/plain;charset=utf-8");
|
|
||||||
inline static const auto s_mimeTypeHtml = QStringLiteral("text/html");
|
|
||||||
inline static const auto s_mimeTypeBmp = QStringLiteral("image/bmp");
|
|
||||||
|
|
||||||
// Additional HTML MIME type variants
|
|
||||||
const char *const s_mimeTypeHtmlUtf8 = "text/html;charset=UTF-8";
|
|
||||||
const char *const s_mimeTypeHtmlWindows = "HTML Format";
|
|
||||||
|
|
||||||
// Command timeout (milliseconds)
|
|
||||||
const int kCacheValidityMs = 100;
|
|
||||||
const int kMonitorIntervalMs = 1000;
|
|
||||||
const int kMaxConsecutiveErrors = 5;
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
WlClipboard::WlClipboard(ClipboardID id) : m_id(id), m_useClipboard(id == kClipboardClipboard)
|
|
||||||
{
|
|
||||||
// Initialize cached data
|
|
||||||
for (int i = 0; i < static_cast<int>(Format::TotalFormats); ++i) {
|
|
||||||
m_cachedAvailable[i] = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
WlClipboard::~WlClipboard()
|
|
||||||
{
|
|
||||||
stopMonitoring();
|
|
||||||
for (auto &cmd : m_runningWlCopies) {
|
|
||||||
cmd->close();
|
|
||||||
cmd->waitForFinished(100);
|
|
||||||
}
|
|
||||||
m_runningWlCopies.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
ClipboardID WlClipboard::getID() const
|
|
||||||
{
|
|
||||||
return m_id;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool WlClipboard::isAvailable()
|
|
||||||
{
|
|
||||||
return !QStandardPaths::findExecutable(s_copyApp).isEmpty() && !QStandardPaths::findExecutable(s_pasteApp).isEmpty();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool WlClipboard::isEnabled()
|
|
||||||
{
|
|
||||||
return Settings::value(Settings::Core::UseWlClipboard).toBool();
|
|
||||||
}
|
|
||||||
|
|
||||||
void WlClipboard::startMonitoring()
|
|
||||||
{
|
|
||||||
if (m_monitoring) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
m_stopMonitoring = false;
|
|
||||||
m_monitoring = true;
|
|
||||||
m_monitorThread = std::make_unique<std::thread>(&WlClipboard::monitorClipboard, this);
|
|
||||||
}
|
|
||||||
|
|
||||||
void WlClipboard::stopMonitoring()
|
|
||||||
{
|
|
||||||
if (!m_monitoring) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
m_stopMonitoring = true;
|
|
||||||
m_monitoring = false;
|
|
||||||
|
|
||||||
if (m_monitorThread && m_monitorThread->joinable()) {
|
|
||||||
m_monitorThread->join();
|
|
||||||
}
|
|
||||||
m_monitorThread.reset();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool WlClipboard::hasChanged() const
|
|
||||||
{
|
|
||||||
return m_hasChanged.load();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool WlClipboard::empty()
|
|
||||||
{
|
|
||||||
if (!m_open) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
auto cmd = new QProcess(this);
|
|
||||||
cmd->setProgram(s_copyApp);
|
|
||||||
m_runningWlCopies.append(cmd);
|
|
||||||
connect(cmd, &QProcess::finished, this, [&] { m_runningWlCopies.removeAll(cmd); });
|
|
||||||
|
|
||||||
QStringList args = {s_noNewLine, ""};
|
|
||||||
if (!m_useClipboard)
|
|
||||||
args.prepend(s_isPrimary);
|
|
||||||
|
|
||||||
cmd->setArguments(args);
|
|
||||||
cmd->start();
|
|
||||||
bool success = cmd->waitForStarted(100);
|
|
||||||
|
|
||||||
if (success) {
|
|
||||||
// Update ownership and cache only if command succeeded
|
|
||||||
std::scoped_lock<std::mutex> lock(m_cacheMutex);
|
|
||||||
updateOwnership(true);
|
|
||||||
invalidateCache();
|
|
||||||
}
|
|
||||||
|
|
||||||
return success;
|
|
||||||
}
|
|
||||||
|
|
||||||
void WlClipboard::add(Format format, const std::string &data)
|
|
||||||
{
|
|
||||||
if (!m_open) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (format == Format::HTML) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto mimeType = formatToMimeType(format);
|
|
||||||
if (mimeType.isEmpty()) {
|
|
||||||
LOG_WARN("unsupported clipboard format: %d", format);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto cmd = new QProcess(this);
|
|
||||||
cmd->setProgram(s_copyApp);
|
|
||||||
|
|
||||||
m_runningWlCopies.append(cmd);
|
|
||||||
connect(cmd, &QProcess::finished, this, [&] { m_runningWlCopies.removeAll(cmd); });
|
|
||||||
|
|
||||||
QStringList args = {s_noNewLine, s_readType.arg(mimeType), QString::fromStdString(data)};
|
|
||||||
if (!m_useClipboard)
|
|
||||||
args.prepend(s_isPrimary);
|
|
||||||
|
|
||||||
cmd->setArguments(args);
|
|
||||||
cmd->start();
|
|
||||||
|
|
||||||
if (cmd->waitForStarted(100)) {
|
|
||||||
std::scoped_lock<std::mutex> lock(m_cacheMutex);
|
|
||||||
updateOwnership(true);
|
|
||||||
invalidateCache();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool WlClipboard::open(Time time) const
|
|
||||||
{
|
|
||||||
if (m_open) {
|
|
||||||
LOG_DEBUG("failed to open clipboard: already opened");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
m_open = true;
|
|
||||||
m_time = time;
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void WlClipboard::close() const
|
|
||||||
{
|
|
||||||
if (!m_open) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
LOG_DEBUG("close clipboard");
|
|
||||||
|
|
||||||
m_open = false;
|
|
||||||
const_cast<WlClipboard *>(this)->invalidateCache();
|
|
||||||
}
|
|
||||||
|
|
||||||
IClipboard::Time WlClipboard::getTime() const
|
|
||||||
{
|
|
||||||
return m_time;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool WlClipboard::has(Format format) const
|
|
||||||
{
|
|
||||||
if (!m_open) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::scoped_lock<std::mutex> lock(m_cacheMutex);
|
|
||||||
|
|
||||||
// Check cache validity
|
|
||||||
Time currentTime = getCurrentTime();
|
|
||||||
if (m_cached && (currentTime - m_cachedTime) < kCacheValidityMs) {
|
|
||||||
return m_cachedAvailable[static_cast<int>(format)];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (const auto availableTypes = getAvailableMimeTypes(); availableTypes.isEmpty()) {
|
|
||||||
// No types available - mark all formats as unavailable
|
|
||||||
for (int i = 0; i < static_cast<int>(Format::TotalFormats); ++i) {
|
|
||||||
m_cachedAvailable[i] = false;
|
|
||||||
m_cachedData[i].clear();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
using enum IClipboard::Format;
|
|
||||||
// Check each format against available types
|
|
||||||
for (int i = 0; i < static_cast<int>(TotalFormats); ++i) {
|
|
||||||
auto currentFormat = static_cast<Format>(i);
|
|
||||||
const auto mimeType = formatToMimeType(currentFormat);
|
|
||||||
|
|
||||||
m_cachedAvailable[i] = false;
|
|
||||||
if (!mimeType.isEmpty()) {
|
|
||||||
for (const auto &available : availableTypes) {
|
|
||||||
if (available == mimeType || (currentFormat == Text && available == QStringLiteral("text/plain")) ||
|
|
||||||
(currentFormat == HTML && available.startsWith(QStringLiteral("text/html")))) {
|
|
||||||
m_cachedAvailable[i] = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
m_cached = true;
|
|
||||||
m_cachedTime = currentTime;
|
|
||||||
|
|
||||||
return m_cachedAvailable[static_cast<int>(format)];
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string WlClipboard::get(Format format) const
|
|
||||||
{
|
|
||||||
if (!m_open) {
|
|
||||||
return std::string();
|
|
||||||
}
|
|
||||||
|
|
||||||
std::scoped_lock<std::mutex> lock(m_cacheMutex);
|
|
||||||
|
|
||||||
// Return cached data if available and valid
|
|
||||||
if (m_cached && m_cachedAvailable[static_cast<int>(format)] && !m_cachedData[static_cast<int>(format)].empty()) {
|
|
||||||
return m_cachedData[static_cast<int>(format)];
|
|
||||||
}
|
|
||||||
|
|
||||||
auto mimeType = formatToMimeType(format);
|
|
||||||
if (mimeType.isEmpty()) {
|
|
||||||
return std::string();
|
|
||||||
}
|
|
||||||
|
|
||||||
QProcess cmd;
|
|
||||||
cmd.setProgram(s_pasteApp);
|
|
||||||
|
|
||||||
QStringList args = {s_noNewLine, s_readType.arg(mimeType)};
|
|
||||||
if (!m_useClipboard)
|
|
||||||
args.append(s_isPrimary);
|
|
||||||
|
|
||||||
cmd.setArguments(args);
|
|
||||||
cmd.start();
|
|
||||||
cmd.waitForFinished();
|
|
||||||
|
|
||||||
auto data = cmd.readAll().toStdString();
|
|
||||||
|
|
||||||
// Update cache
|
|
||||||
m_cachedData[static_cast<int>(format)] = data;
|
|
||||||
m_cachedAvailable[static_cast<int>(format)] = !data.empty();
|
|
||||||
m_cached = true;
|
|
||||||
m_cachedTime = getCurrentTime();
|
|
||||||
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
|
|
||||||
QString WlClipboard::formatToMimeType(Format format) const
|
|
||||||
{
|
|
||||||
switch (format) {
|
|
||||||
using enum IClipboard::Format;
|
|
||||||
case Text:
|
|
||||||
return s_mimeTypeText;
|
|
||||||
case HTML:
|
|
||||||
return s_mimeTypeHtml;
|
|
||||||
case Bitmap:
|
|
||||||
return s_mimeTypeBmp;
|
|
||||||
default:
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
IClipboard::Format WlClipboard::mimeTypeToFormat(const QString &mimeType) const
|
|
||||||
{
|
|
||||||
using enum IClipboard::Format;
|
|
||||||
if (mimeType == s_mimeTypeText || mimeType == QStringLiteral("text/plain")) {
|
|
||||||
return Text;
|
|
||||||
}
|
|
||||||
if (mimeType == s_mimeTypeHtml || mimeType == s_mimeTypeHtmlUtf8 || mimeType == s_mimeTypeHtmlWindows ||
|
|
||||||
mimeType.contains("text/html")) {
|
|
||||||
return HTML;
|
|
||||||
}
|
|
||||||
if (mimeType == s_mimeTypeBmp) {
|
|
||||||
return Bitmap;
|
|
||||||
}
|
|
||||||
return Text; // Default fallback
|
|
||||||
}
|
|
||||||
|
|
||||||
QStringList WlClipboard::getAvailableMimeTypes() const
|
|
||||||
{
|
|
||||||
QProcess cmd;
|
|
||||||
cmd.setProgram(s_pasteApp);
|
|
||||||
|
|
||||||
QStringList args = {s_listTypes};
|
|
||||||
if (!m_useClipboard)
|
|
||||||
args.append(s_isPrimary);
|
|
||||||
|
|
||||||
cmd.setArguments(args);
|
|
||||||
cmd.start();
|
|
||||||
cmd.waitForFinished();
|
|
||||||
|
|
||||||
const static QChar newLine = QLatin1Char('\n');
|
|
||||||
return QString::fromLocal8Bit(cmd.readAll()).split(newLine);
|
|
||||||
}
|
|
||||||
|
|
||||||
void WlClipboard::monitorClipboard()
|
|
||||||
{
|
|
||||||
QStringList lastTypes;
|
|
||||||
int consecutiveErrors = 0;
|
|
||||||
|
|
||||||
while (!m_stopMonitoring) {
|
|
||||||
std::this_thread::sleep_for(std::chrono::milliseconds(kMonitorIntervalMs));
|
|
||||||
try {
|
|
||||||
// Check if clipboard content has changed by comparing available types
|
|
||||||
const auto currentTypes = getAvailableMimeTypes();
|
|
||||||
|
|
||||||
// Reset error counter on successful operation
|
|
||||||
consecutiveErrors = 0;
|
|
||||||
|
|
||||||
if (currentTypes != lastTypes) {
|
|
||||||
m_hasChanged = true;
|
|
||||||
lastTypes = currentTypes;
|
|
||||||
|
|
||||||
// Clear cache when clipboard changes
|
|
||||||
std::scoped_lock<std::mutex> lock(m_cacheMutex);
|
|
||||||
invalidateCache();
|
|
||||||
updateOwnership(false);
|
|
||||||
}
|
|
||||||
} catch (const std::exception &e) {
|
|
||||||
LOG_WARN("clipboard monitoring error: %s", e.what());
|
|
||||||
if (++consecutiveErrors >= kMaxConsecutiveErrors) {
|
|
||||||
LOG_ERR("too many consecutive errors in clipboard monitoring, stopping");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
} catch (...) {
|
|
||||||
LOG_WARN("clipboard monitoring unknown error");
|
|
||||||
if (++consecutiveErrors >= kMaxConsecutiveErrors) {
|
|
||||||
LOG_ERR("too many consecutive errors in clipboard monitoring, stopping");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
IClipboard::Time WlClipboard::getCurrentTime() const
|
|
||||||
{
|
|
||||||
return static_cast<Time>(QDateTime::currentMSecsSinceEpoch());
|
|
||||||
}
|
|
||||||
|
|
||||||
bool WlClipboard::isOwned() const
|
|
||||||
{
|
|
||||||
return m_owned;
|
|
||||||
}
|
|
||||||
|
|
||||||
void WlClipboard::resetChanged()
|
|
||||||
{
|
|
||||||
m_hasChanged = false;
|
|
||||||
|
|
||||||
// Clear cache when resetting change flag to force fresh data retrieval
|
|
||||||
std::scoped_lock<std::mutex> lock(m_cacheMutex);
|
|
||||||
invalidateCache();
|
|
||||||
}
|
|
||||||
|
|
||||||
void WlClipboard::updateOwnership(bool owned)
|
|
||||||
{
|
|
||||||
m_owned = owned;
|
|
||||||
}
|
|
||||||
|
|
||||||
void WlClipboard::invalidateCache()
|
|
||||||
{
|
|
||||||
m_cached = false;
|
|
||||||
m_cachedTime = 0;
|
|
||||||
for (int i = 0; i < static_cast<int>(Format::TotalFormats); ++i) {
|
|
||||||
m_cachedData[i].clear();
|
|
||||||
m_cachedAvailable[i] = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,119 +0,0 @@
|
||||||
/*
|
|
||||||
* Deskflow -- mouse and keyboard sharing utility
|
|
||||||
* SPDX-FileCopyrightText: (C) 2025 Deskflow Developers
|
|
||||||
* SPDX-License-Identifier: GPL-2.0-only WITH LicenseRef-OpenSSL-Exception
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include "deskflow/ClipboardTypes.h"
|
|
||||||
#include "deskflow/IClipboard.h"
|
|
||||||
|
|
||||||
#include <atomic>
|
|
||||||
#include <fcntl.h>
|
|
||||||
#include <memory>
|
|
||||||
#include <mutex>
|
|
||||||
#include <string>
|
|
||||||
#include <thread>
|
|
||||||
|
|
||||||
#include <QObject>
|
|
||||||
#include <QString>
|
|
||||||
|
|
||||||
class QProcess;
|
|
||||||
//! Wayland clipboard implementation using wl-copy/wl-paste
|
|
||||||
/*!
|
|
||||||
This class implements clipboard functionality for Wayland environments
|
|
||||||
by using the wl-clipboard utilities (wl-copy and wl-paste).
|
|
||||||
*/
|
|
||||||
class WlClipboard : public QObject, public IClipboard
|
|
||||||
{
|
|
||||||
Q_OBJECT
|
|
||||||
public:
|
|
||||||
explicit WlClipboard(ClipboardID id);
|
|
||||||
WlClipboard(WlClipboard const &) = delete;
|
|
||||||
WlClipboard(WlClipboard &&) = delete;
|
|
||||||
~WlClipboard() override;
|
|
||||||
|
|
||||||
WlClipboard &operator=(WlClipboard const &) = delete;
|
|
||||||
WlClipboard &operator=(WlClipboard &&) = delete;
|
|
||||||
|
|
||||||
//! Get clipboard ID
|
|
||||||
ClipboardID getID() const;
|
|
||||||
|
|
||||||
//! Check if wl-clipboard tools are available
|
|
||||||
static bool isAvailable();
|
|
||||||
|
|
||||||
//! Check if WlClipboard is enabled
|
|
||||||
static bool isEnabled();
|
|
||||||
|
|
||||||
//! Start monitoring clipboard changes
|
|
||||||
void startMonitoring();
|
|
||||||
|
|
||||||
//! Stop monitoring clipboard changes
|
|
||||||
void stopMonitoring();
|
|
||||||
|
|
||||||
//! Check if clipboard has changed
|
|
||||||
bool hasChanged() const;
|
|
||||||
|
|
||||||
//! Reset the changed flag and clear cache
|
|
||||||
void resetChanged();
|
|
||||||
|
|
||||||
// IClipboard overrides
|
|
||||||
bool empty() override;
|
|
||||||
void add(Format format, const std::string &data) override;
|
|
||||||
bool open(Time time) const override;
|
|
||||||
void close() const override;
|
|
||||||
Time getTime() const override;
|
|
||||||
bool has(Format format) const override;
|
|
||||||
std::string get(Format format) const override;
|
|
||||||
|
|
||||||
private:
|
|
||||||
//! Convert IClipboard format to MIME type
|
|
||||||
QString formatToMimeType(Format format) const;
|
|
||||||
|
|
||||||
//! Convert MIME type to IClipboard format
|
|
||||||
Format mimeTypeToFormat(const QString &mimeType) const;
|
|
||||||
|
|
||||||
//! Get available MIME types from clipboard
|
|
||||||
QStringList getAvailableMimeTypes() const;
|
|
||||||
|
|
||||||
//! Monitor clipboard changes in background thread
|
|
||||||
void monitorClipboard();
|
|
||||||
|
|
||||||
//! Get current clipboard serial/timestamp
|
|
||||||
Time getCurrentTime() const;
|
|
||||||
|
|
||||||
//! Check if we own the clipboard
|
|
||||||
bool isOwned() const;
|
|
||||||
|
|
||||||
//! Update our ownership status
|
|
||||||
void updateOwnership(bool owned);
|
|
||||||
|
|
||||||
//! Invalidate cached clipboard data
|
|
||||||
void invalidateCache();
|
|
||||||
|
|
||||||
private:
|
|
||||||
ClipboardID m_id;
|
|
||||||
mutable bool m_open = false;
|
|
||||||
mutable Time m_time = 0;
|
|
||||||
mutable Time m_cachedTime = 0;
|
|
||||||
mutable bool m_owned = false;
|
|
||||||
mutable std::atomic<bool> m_hasChanged = false;
|
|
||||||
|
|
||||||
// Cached clipboard data
|
|
||||||
mutable std::mutex m_cacheMutex;
|
|
||||||
mutable bool m_cached = false;
|
|
||||||
mutable std::string m_cachedData[static_cast<int>(Format::TotalFormats)];
|
|
||||||
mutable bool m_cachedAvailable[static_cast<int>(Format::TotalFormats)];
|
|
||||||
|
|
||||||
// Background monitoring
|
|
||||||
std::unique_ptr<std::thread> m_monitorThread;
|
|
||||||
std::atomic<bool> m_monitoring = false;
|
|
||||||
std::atomic<bool> m_stopMonitoring = false;
|
|
||||||
|
|
||||||
// Clipboard selection type (true = clipboard, false = primary)
|
|
||||||
bool m_useClipboard;
|
|
||||||
|
|
||||||
// Hold a ref to running wl-copy processes
|
|
||||||
QList<QProcess *> m_runningWlCopies;
|
|
||||||
};
|
|
||||||
|
|
@ -1,138 +0,0 @@
|
||||||
/*
|
|
||||||
* Deskflow -- mouse and keyboard sharing utility
|
|
||||||
* SPDX-FileCopyrightText: (C) 2025 Deskflow Developers
|
|
||||||
* SPDX-License-Identifier: GPL-2.0-only WITH LicenseRef-OpenSSL-Exception
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "platform/WlClipboardCollection.h"
|
|
||||||
|
|
||||||
#include "base/Log.h"
|
|
||||||
#include "deskflow/ClipboardTypes.h"
|
|
||||||
|
|
||||||
namespace deskflow {
|
|
||||||
|
|
||||||
WlClipboardCollection::WlClipboardCollection()
|
|
||||||
{
|
|
||||||
initialize();
|
|
||||||
}
|
|
||||||
|
|
||||||
WlClipboardCollection::~WlClipboardCollection()
|
|
||||||
{
|
|
||||||
cleanup();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool WlClipboardCollection::isAvailable() const
|
|
||||||
{
|
|
||||||
return m_available;
|
|
||||||
}
|
|
||||||
|
|
||||||
IClipboard *WlClipboardCollection::getClipboard(ClipboardID id) const
|
|
||||||
{
|
|
||||||
if (!m_available || id >= m_clipboards.size()) {
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
return m_clipboards[id].get();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool WlClipboardCollection::hasChanged() const
|
|
||||||
{
|
|
||||||
if (!m_available) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const auto &clipboard : m_clipboards) {
|
|
||||||
if (clipboard && clipboard->hasChanged()) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
void WlClipboardCollection::startMonitoring()
|
|
||||||
{
|
|
||||||
if (!m_available || m_monitoring) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const auto &clipboard : m_clipboards) {
|
|
||||||
if (clipboard) {
|
|
||||||
clipboard->startMonitoring();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
m_monitoring = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void WlClipboardCollection::stopMonitoring()
|
|
||||||
{
|
|
||||||
if (!m_available || !m_monitoring) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const auto &clipboard : m_clipboards) {
|
|
||||||
if (clipboard) {
|
|
||||||
clipboard->stopMonitoring();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
m_monitoring = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
void WlClipboardCollection::resetChanged() const
|
|
||||||
{
|
|
||||||
if (!m_available) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const auto &clipboard : m_clipboards) {
|
|
||||||
if (clipboard) {
|
|
||||||
clipboard->resetChanged();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void WlClipboardCollection::initialize()
|
|
||||||
{
|
|
||||||
if (!WlClipboard::isEnabled()) {
|
|
||||||
LOG_DEBUG("wl-clipboard setting disabled");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!WlClipboard::isAvailable()) {
|
|
||||||
LOG_WARN("wl-clipboard tools not found, clipboard functionality disabled");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create clipboard instances for each clipboard type
|
|
||||||
m_clipboards.resize(kClipboardEnd);
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Primary clipboard (selection)
|
|
||||||
m_clipboards[kClipboardSelection] = std::make_unique<WlClipboard>(kClipboardSelection);
|
|
||||||
|
|
||||||
// Standard clipboard
|
|
||||||
m_clipboards[kClipboardClipboard] = std::make_unique<WlClipboard>(kClipboardClipboard);
|
|
||||||
|
|
||||||
m_available = true;
|
|
||||||
LOG_VERBOSE("initialized Wayland clipboard support");
|
|
||||||
|
|
||||||
} catch (const std::exception &e) {
|
|
||||||
LOG_ERR("failed to initialize clipboard: %s", e.what());
|
|
||||||
cleanup();
|
|
||||||
m_available = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void WlClipboardCollection::cleanup()
|
|
||||||
{
|
|
||||||
if (m_monitoring) {
|
|
||||||
stopMonitoring();
|
|
||||||
}
|
|
||||||
|
|
||||||
m_clipboards.clear();
|
|
||||||
m_available = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace deskflow
|
|
||||||
|
|
@ -1,61 +0,0 @@
|
||||||
/*
|
|
||||||
* Deskflow -- mouse and keyboard sharing utility
|
|
||||||
* SPDX-FileCopyrightText: (C) 2025 Deskflow Developers
|
|
||||||
* SPDX-License-Identifier: GPL-2.0-only WITH LicenseRef-OpenSSL-Exception
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include "deskflow/ClipboardTypes.h"
|
|
||||||
#include "deskflow/IClipboard.h"
|
|
||||||
#include "platform/WlClipboard.h"
|
|
||||||
|
|
||||||
#include <memory>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
namespace deskflow {
|
|
||||||
|
|
||||||
//! Clipboard manager for EiScreen
|
|
||||||
/*!
|
|
||||||
This class manages clipboard operations for the EiScreen implementation.
|
|
||||||
It automatically detects the best available clipboard backend and provides
|
|
||||||
a unified interface for clipboard operations.
|
|
||||||
*/
|
|
||||||
class WlClipboardCollection
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
WlClipboardCollection();
|
|
||||||
~WlClipboardCollection();
|
|
||||||
|
|
||||||
//! Check if clipboard functionality is available
|
|
||||||
bool isAvailable() const;
|
|
||||||
|
|
||||||
//! Get clipboard for specific ID
|
|
||||||
IClipboard *getClipboard(ClipboardID id) const;
|
|
||||||
|
|
||||||
//! Check if any clipboard has changed
|
|
||||||
bool hasChanged() const;
|
|
||||||
|
|
||||||
//! Start monitoring clipboard changes
|
|
||||||
void startMonitoring();
|
|
||||||
|
|
||||||
//! Stop monitoring clipboard changes
|
|
||||||
void stopMonitoring();
|
|
||||||
|
|
||||||
//! Reset change detection
|
|
||||||
void resetChanged() const;
|
|
||||||
|
|
||||||
private:
|
|
||||||
//! Initialize clipboard backends
|
|
||||||
void initialize();
|
|
||||||
|
|
||||||
//! Cleanup clipboard backends
|
|
||||||
void cleanup();
|
|
||||||
|
|
||||||
private:
|
|
||||||
std::vector<std::unique_ptr<WlClipboard>> m_clipboards;
|
|
||||||
bool m_available = false;
|
|
||||||
bool m_monitoring = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace deskflow
|
|
||||||
|
|
@ -34,15 +34,4 @@ elseif(UNIX)
|
||||||
WORKING_DIRECTORY "${CMAKE_BINARY_DIR}/src/lib/platform"
|
WORKING_DIRECTORY "${CMAKE_BINARY_DIR}/src/lib/platform"
|
||||||
)
|
)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
# Add Wayland clipboard tests when Wayland support is available
|
|
||||||
if(LIBEI_FOUND AND LIBPORTAL_FOUND)
|
|
||||||
create_test(
|
|
||||||
NAME WlClipboardTests
|
|
||||||
DEPENDS platform
|
|
||||||
LIBS base arch
|
|
||||||
SOURCE WlClipboardTests.cpp
|
|
||||||
WORKING_DIRECTORY "${CMAKE_BINARY_DIR}/src/lib/platform"
|
|
||||||
)
|
|
||||||
endif()
|
|
||||||
endif()
|
endif()
|
||||||
|
|
|
||||||
|
|
@ -1,377 +0,0 @@
|
||||||
/*
|
|
||||||
* Deskflow -- mouse and keyboard sharing utility
|
|
||||||
* SPDX-FileCopyrightText: (C) 2025 Deskflow Developers
|
|
||||||
* SPDX-License-Identifier: GPL-2.0-only WITH LicenseRef-OpenSSL-Exception
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "WlClipboardTests.h"
|
|
||||||
|
|
||||||
#include "common/LogLevel.h"
|
|
||||||
#include "common/PlatformInfo.h"
|
|
||||||
#include "deskflow/ClipboardTypes.h"
|
|
||||||
#include "platform/WlClipboard.h"
|
|
||||||
|
|
||||||
#include <chrono>
|
|
||||||
#include <functional>
|
|
||||||
#include <thread>
|
|
||||||
|
|
||||||
#include <QTest>
|
|
||||||
|
|
||||||
void WlClipboardTests::defaultCtor()
|
|
||||||
{
|
|
||||||
WlClipboard clipboard(kClipboardClipboard);
|
|
||||||
QCOMPARE(kClipboardClipboard, clipboard.getID());
|
|
||||||
|
|
||||||
WlClipboard primaryClipboard(kClipboardSelection);
|
|
||||||
QCOMPARE(kClipboardSelection, primaryClipboard.getID());
|
|
||||||
}
|
|
||||||
|
|
||||||
void WlClipboardTests::isAvailable()
|
|
||||||
{
|
|
||||||
// This test may fail on systems without wl-clipboard installed
|
|
||||||
// In CI environments, we might need to skip this test or mock the commands
|
|
||||||
bool available = WlClipboard::isAvailable();
|
|
||||||
|
|
||||||
// We don't assert true/false here because it depends on system setup
|
|
||||||
// Just verify the method doesn't crash and returns a boolean
|
|
||||||
QVERIFY(available == true || available == false);
|
|
||||||
}
|
|
||||||
|
|
||||||
void WlClipboardTests::initTestCase()
|
|
||||||
{
|
|
||||||
m_arch.init();
|
|
||||||
m_log.setFilter(LogLevel::Level::Verbose);
|
|
||||||
|
|
||||||
// Only run tests if Wayland clipboard tools are available
|
|
||||||
if (!deskflow::platform::isWayland()) {
|
|
||||||
QSKIP("not running on wayland skipping Wayland clipboard tests");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Only run tests if Wayland clipboard tools are available
|
|
||||||
if (!WlClipboard::isAvailable()) {
|
|
||||||
QSKIP("wl-clipboard tools not available, skipping Wayland clipboard tests");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test if Wayland commands actually work (not just exist)
|
|
||||||
WlClipboard testClipboard(kClipboardClipboard);
|
|
||||||
if (!testClipboard.open(0)) {
|
|
||||||
QSKIP("Failed to open Wayland clipboard, likely no Wayland session");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Try to clear clipboard - if this fails, we're probably not in a Wayland environment
|
|
||||||
if (!testClipboard.empty()) {
|
|
||||||
testClipboard.close();
|
|
||||||
QSKIP("Wayland clipboard operations not working, skipping tests");
|
|
||||||
}
|
|
||||||
|
|
||||||
testClipboard.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
void WlClipboardTests::cleanupTestCase()
|
|
||||||
{
|
|
||||||
// Clean up by emptying clipboards
|
|
||||||
try {
|
|
||||||
WlClipboard clipboard(kClipboardClipboard);
|
|
||||||
if (clipboard.open(0)) {
|
|
||||||
clipboard.empty();
|
|
||||||
clipboard.close();
|
|
||||||
}
|
|
||||||
} catch (...) {
|
|
||||||
// Ignore cleanup errors
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void WlClipboardTests::open()
|
|
||||||
{
|
|
||||||
WlClipboard clipboard(kClipboardClipboard);
|
|
||||||
QVERIFY(clipboard.open(0));
|
|
||||||
|
|
||||||
// Opening again should return false
|
|
||||||
QVERIFY(!clipboard.open(1));
|
|
||||||
|
|
||||||
clipboard.close();
|
|
||||||
|
|
||||||
// Should be able to open again after closing
|
|
||||||
QVERIFY(clipboard.open(2));
|
|
||||||
clipboard.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
void WlClipboardTests::empty()
|
|
||||||
{
|
|
||||||
WlClipboard clipboard(kClipboardClipboard);
|
|
||||||
QVERIFY(clipboard.open(0));
|
|
||||||
|
|
||||||
// First add some known content
|
|
||||||
clipboard.add(IClipboard::Format::Text, m_testString);
|
|
||||||
QVERIFY(clipboard.has(IClipboard::Format::Text));
|
|
||||||
QCOMPARE(clipboard.get(IClipboard::Format::Text), m_testString);
|
|
||||||
|
|
||||||
// Now empty the clipboard
|
|
||||||
QVERIFY(clipboard.empty());
|
|
||||||
|
|
||||||
// Wait for the empty operation to complete and verify our content is gone
|
|
||||||
// Use longer timeout for clipboard operations
|
|
||||||
QVERIFY(waitForClipboardEmpty(clipboard, m_testString, 3000));
|
|
||||||
clipboard.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
void WlClipboardTests::singleFormat()
|
|
||||||
{
|
|
||||||
WlClipboard clipboard(kClipboardClipboard);
|
|
||||||
QVERIFY(clipboard.open(0));
|
|
||||||
|
|
||||||
// Clear clipboard first - if this fails, skip test
|
|
||||||
if (!clipboard.empty()) {
|
|
||||||
clipboard.close();
|
|
||||||
QSKIP("Wayland clipboard operations not working in test environment");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add text data
|
|
||||||
clipboard.add(IClipboard::Format::Text, m_testString);
|
|
||||||
|
|
||||||
// Wait for the data to be available
|
|
||||||
if (!waitForClipboardContent(clipboard, IClipboard::Format::Text, m_testString)) {
|
|
||||||
clipboard.close();
|
|
||||||
QSKIP("Wl-clipboard did not receive contents in time");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add different text data - this should replace the previous data
|
|
||||||
if (!clipboard.empty()) {
|
|
||||||
clipboard.close();
|
|
||||||
QSKIP("Wayland clipboard clear operations not working in test environment");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Skip the wait for empty since we know clipboard operations aren't fully functional
|
|
||||||
clipboard.add(IClipboard::Format::Text, m_testString2);
|
|
||||||
|
|
||||||
clipboard.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
void WlClipboardTests::multipleFormats()
|
|
||||||
{
|
|
||||||
WlClipboard clipboard(kClipboardClipboard);
|
|
||||||
QVERIFY(clipboard.open(0));
|
|
||||||
|
|
||||||
// Clear clipboard first
|
|
||||||
QVERIFY(clipboard.empty());
|
|
||||||
|
|
||||||
// Add text data first
|
|
||||||
clipboard.add(IClipboard::Format::Text, m_testString);
|
|
||||||
|
|
||||||
// Note: wl-clipboard typically handles one format at a time
|
|
||||||
// So we test formats separately rather than simultaneously
|
|
||||||
if (!waitForClipboardContent(clipboard, IClipboard::Format::Text, m_testString)) {
|
|
||||||
clipboard.close();
|
|
||||||
QSKIP("Wl-clipboard did not receive contents in time");
|
|
||||||
}
|
|
||||||
|
|
||||||
// HTML format is currently not supported in WlClipboard implementation
|
|
||||||
// So we skip HTML testing and just verify text format works
|
|
||||||
|
|
||||||
// Clear and add different text data to test format replacement
|
|
||||||
QVERIFY(clipboard.empty());
|
|
||||||
QVERIFY(waitForClipboardEmpty(clipboard, m_testString));
|
|
||||||
|
|
||||||
clipboard.add(IClipboard::Format::Text, m_testString2);
|
|
||||||
if (!waitForClipboardContent(clipboard, IClipboard::Format::Text, m_testString2)) {
|
|
||||||
clipboard.close();
|
|
||||||
QSKIP("Wl-clipboard did not receive contents in time");
|
|
||||||
}
|
|
||||||
|
|
||||||
clipboard.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
void WlClipboardTests::hasFormat()
|
|
||||||
{
|
|
||||||
WlClipboard clipboard(kClipboardClipboard);
|
|
||||||
QVERIFY(clipboard.open(0));
|
|
||||||
|
|
||||||
// Clear clipboard first
|
|
||||||
QVERIFY(clipboard.empty());
|
|
||||||
|
|
||||||
// Wait for empty to take effect with longer timeout
|
|
||||||
QVERIFY(waitForClipboardEmpty(clipboard, "any", 3000));
|
|
||||||
|
|
||||||
// Initially should not have any formats
|
|
||||||
QVERIFY(!clipboard.has(IClipboard::Format::Text));
|
|
||||||
QVERIFY(!clipboard.has(IClipboard::Format::HTML));
|
|
||||||
QVERIFY(!clipboard.has(IClipboard::Format::Bitmap));
|
|
||||||
|
|
||||||
// Add text and verify
|
|
||||||
clipboard.add(IClipboard::Format::Text, m_testString);
|
|
||||||
if (!waitForClipboardContent(clipboard, IClipboard::Format::Text, m_testString)) {
|
|
||||||
clipboard.close();
|
|
||||||
QSKIP("Wl-clipboard did not receive contents in time");
|
|
||||||
}
|
|
||||||
QVERIFY(clipboard.has(IClipboard::Format::Text));
|
|
||||||
QVERIFY(!clipboard.has(IClipboard::Format::HTML)); // HTML not supported
|
|
||||||
QVERIFY(!clipboard.has(IClipboard::Format::Bitmap));
|
|
||||||
|
|
||||||
clipboard.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
void WlClipboardTests::getTime()
|
|
||||||
{
|
|
||||||
WlClipboard clipboard(kClipboardClipboard);
|
|
||||||
QVERIFY(clipboard.open(100));
|
|
||||||
|
|
||||||
// Should return the time passed to open()
|
|
||||||
QCOMPARE(clipboard.getTime(), static_cast<IClipboard::Time>(100));
|
|
||||||
|
|
||||||
clipboard.close();
|
|
||||||
QVERIFY(clipboard.open(200));
|
|
||||||
QCOMPARE(clipboard.getTime(), static_cast<IClipboard::Time>(200));
|
|
||||||
clipboard.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
void WlClipboardTests::monitoring()
|
|
||||||
{
|
|
||||||
WlClipboard clipboard(kClipboardClipboard);
|
|
||||||
QVERIFY(clipboard.open(0));
|
|
||||||
|
|
||||||
// Clear clipboard first
|
|
||||||
QVERIFY(clipboard.empty());
|
|
||||||
|
|
||||||
// Start monitoring
|
|
||||||
clipboard.startMonitoring();
|
|
||||||
|
|
||||||
// Initially should not have changed
|
|
||||||
QVERIFY(!clipboard.hasChanged());
|
|
||||||
|
|
||||||
// Make a change to the clipboard using a separate clipboard instance
|
|
||||||
// to simulate external changes
|
|
||||||
WlClipboard externalClipboard(kClipboardClipboard);
|
|
||||||
if (externalClipboard.open(1)) {
|
|
||||||
externalClipboard.empty();
|
|
||||||
externalClipboard.add(IClipboard::Format::Text, m_testString);
|
|
||||||
externalClipboard.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wait for monitoring thread to detect change
|
|
||||||
auto changeDetected = [&clipboard]() { return clipboard.hasChanged(); };
|
|
||||||
waitForClipboardCondition(clipboard, changeDetected, 1000);
|
|
||||||
|
|
||||||
// Stop monitoring
|
|
||||||
clipboard.stopMonitoring();
|
|
||||||
clipboard.close();
|
|
||||||
|
|
||||||
// Note: Change detection might not work reliably in all test environments
|
|
||||||
// This test mainly verifies that monitoring doesn't crash
|
|
||||||
}
|
|
||||||
|
|
||||||
void WlClipboardTests::primaryClipboard()
|
|
||||||
{
|
|
||||||
// Test that primary clipboard works independently from regular clipboard
|
|
||||||
WlClipboard clipboard(kClipboardClipboard);
|
|
||||||
WlClipboard primaryClipboard(kClipboardSelection);
|
|
||||||
|
|
||||||
QVERIFY(clipboard.open(0));
|
|
||||||
QVERIFY(primaryClipboard.open(1));
|
|
||||||
|
|
||||||
// Clear both clipboards
|
|
||||||
QVERIFY(clipboard.empty());
|
|
||||||
QVERIFY(primaryClipboard.empty());
|
|
||||||
|
|
||||||
// Add different data to each
|
|
||||||
clipboard.add(IClipboard::Format::Text, m_testString);
|
|
||||||
primaryClipboard.add(IClipboard::Format::Text, m_testString2);
|
|
||||||
|
|
||||||
if (!waitForClipboardContent(clipboard, IClipboard::Format::Text, m_testString) ||
|
|
||||||
!waitForClipboardContent(primaryClipboard, IClipboard::Format::Text, m_testString2)) {
|
|
||||||
clipboard.close();
|
|
||||||
primaryClipboard.close();
|
|
||||||
QSKIP("Wl-clipboard did not receive contents in time");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Make sure they are different
|
|
||||||
QCOMPARE_NE(clipboard.get(IClipboard::Format::Text), primaryClipboard.get(IClipboard::Format::Text));
|
|
||||||
|
|
||||||
clipboard.close();
|
|
||||||
primaryClipboard.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
void WlClipboardTests::closeWithoutOpen()
|
|
||||||
{
|
|
||||||
WlClipboard clipboard(kClipboardClipboard);
|
|
||||||
|
|
||||||
// Should be safe to call close without open
|
|
||||||
clipboard.close();
|
|
||||||
|
|
||||||
// After close, open should work
|
|
||||||
QVERIFY(clipboard.open(0));
|
|
||||||
clipboard.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
void WlClipboardTests::addWithoutOpen()
|
|
||||||
{
|
|
||||||
WlClipboard clipboard(kClipboardClipboard);
|
|
||||||
|
|
||||||
// Should not crash when adding without open
|
|
||||||
clipboard.add(IClipboard::Format::Text, m_testString);
|
|
||||||
|
|
||||||
// Should still be able to open and use normally
|
|
||||||
QVERIFY(clipboard.open(0));
|
|
||||||
clipboard.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
void WlClipboardTests::getWithoutOpen()
|
|
||||||
{
|
|
||||||
WlClipboard clipboard(kClipboardClipboard);
|
|
||||||
|
|
||||||
// Should return empty string when getting without open
|
|
||||||
std::string result = clipboard.get(IClipboard::Format::Text);
|
|
||||||
QVERIFY(result.empty());
|
|
||||||
|
|
||||||
// Should return false for has() without open
|
|
||||||
QVERIFY(!clipboard.has(IClipboard::Format::Text));
|
|
||||||
|
|
||||||
// Should still be able to open and use normally
|
|
||||||
QVERIFY(clipboard.open(0));
|
|
||||||
clipboard.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool WlClipboardTests::waitForClipboardCondition(WlClipboard &, std::function<bool()> condition, int timeoutMs)
|
|
||||||
{
|
|
||||||
auto startTime = std::chrono::steady_clock::now();
|
|
||||||
auto timeout = std::chrono::milliseconds(timeoutMs);
|
|
||||||
|
|
||||||
while (std::chrono::steady_clock::now() - startTime < timeout) {
|
|
||||||
if (condition()) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool WlClipboardTests::waitForClipboardEmpty(WlClipboard &clipboard, const std::string &previousContent, int timeoutMs)
|
|
||||||
{
|
|
||||||
auto condition = [&clipboard, &previousContent]() {
|
|
||||||
// Check if clipboard is empty or no longer contains our previous content
|
|
||||||
if (!clipboard.has(IClipboard::Format::Text)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
std::string currentContent = clipboard.get(IClipboard::Format::Text);
|
|
||||||
// Consider it empty if content is empty or significantly different
|
|
||||||
return currentContent.empty() || currentContent != previousContent;
|
|
||||||
};
|
|
||||||
|
|
||||||
return waitForClipboardCondition(clipboard, condition, timeoutMs);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool WlClipboardTests::waitForClipboardContent(
|
|
||||||
WlClipboard &clipboard, IClipboard::Format format, const std::string &expectedContent, int timeoutMs
|
|
||||||
)
|
|
||||||
{
|
|
||||||
auto condition = [&clipboard, format, &expectedContent]() {
|
|
||||||
if (!clipboard.has(format)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return clipboard.get(format) == expectedContent;
|
|
||||||
};
|
|
||||||
|
|
||||||
return waitForClipboardCondition(clipboard, condition, timeoutMs);
|
|
||||||
}
|
|
||||||
|
|
||||||
QTEST_MAIN(WlClipboardTests)
|
|
||||||
|
|
@ -1,71 +0,0 @@
|
||||||
/*
|
|
||||||
* Deskflow -- mouse and keyboard sharing utility
|
|
||||||
* SPDX-FileCopyrightText: (C) 2025 Deskflow Developers
|
|
||||||
* SPDX-License-Identifier: GPL-2.0-only WITH LicenseRef-OpenSSL-Exception
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include "arch/Arch.h"
|
|
||||||
#include "base/Log.h"
|
|
||||||
#include "deskflow/ClipboardTypes.h"
|
|
||||||
|
|
||||||
#if WINAPI_LIBEI || WINAPI_PORTAL
|
|
||||||
#include "platform/WlClipboard.h"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include <QTest>
|
|
||||||
#include <functional>
|
|
||||||
|
|
||||||
//! Unit tests for WlClipboard class
|
|
||||||
/*!
|
|
||||||
Tests the Wayland clipboard implementation that uses wl-copy and wl-paste
|
|
||||||
utilities for clipboard operations. These tests verify basic functionality
|
|
||||||
like opening/closing the clipboard, adding/getting data in different formats,
|
|
||||||
and monitoring clipboard changes.
|
|
||||||
|
|
||||||
Note: These tests require wl-clipboard tools to be installed and will be
|
|
||||||
skipped if they are not available or if Wayland support is not compiled in.
|
|
||||||
*/
|
|
||||||
class WlClipboardTests : public QObject
|
|
||||||
{
|
|
||||||
Q_OBJECT
|
|
||||||
private Q_SLOTS:
|
|
||||||
// Test constructor and basic availability
|
|
||||||
void defaultCtor();
|
|
||||||
void isAvailable();
|
|
||||||
|
|
||||||
// Core clipboard functionality tests
|
|
||||||
#if WINAPI_LIBEI || WINAPI_PORTAL
|
|
||||||
void initTestCase();
|
|
||||||
void cleanupTestCase();
|
|
||||||
void open();
|
|
||||||
void empty();
|
|
||||||
void singleFormat();
|
|
||||||
void multipleFormats();
|
|
||||||
void hasFormat();
|
|
||||||
void getTime();
|
|
||||||
void monitoring();
|
|
||||||
void primaryClipboard();
|
|
||||||
void closeWithoutOpen();
|
|
||||||
void addWithoutOpen();
|
|
||||||
void getWithoutOpen();
|
|
||||||
#endif
|
|
||||||
|
|
||||||
private:
|
|
||||||
Arch m_arch;
|
|
||||||
Log m_log;
|
|
||||||
|
|
||||||
#if WINAPI_LIBEI || WINAPI_PORTAL
|
|
||||||
const std::string m_testString = "deskflow test string";
|
|
||||||
const std::string m_testString2 = "Another test string";
|
|
||||||
const std::string m_testHtml = "<html><body>Test HTML</body></html>";
|
|
||||||
|
|
||||||
// clang-format off
|
|
||||||
// Helper methods for robust testing
|
|
||||||
bool waitForClipboardCondition(WlClipboard &clipboard, std::function<bool()> condition, int timeoutMs = 2000);
|
|
||||||
bool waitForClipboardEmpty(WlClipboard &clipboard, const std::string &previousContent, int timeoutMs = 2000);
|
|
||||||
bool waitForClipboardContent(WlClipboard &clipboard, IClipboard::Format format, const std::string &expectedContent, int timeoutMs = 2000);
|
|
||||||
// clang-format on
|
|
||||||
#endif
|
|
||||||
};
|
|
||||||
|
|
@ -1260,14 +1260,6 @@ Al habilitar esta opción, se deshabilitará la interfaz gráfica de usuario (GU
|
||||||
<source>Enable GUI debug messages</source>
|
<source>Enable GUI debug messages</source>
|
||||||
<translation type="unfinished">Habilitar mensajes de depuración de la interfaz gráfica de usuario</translation>
|
<translation type="unfinished">Habilitar mensajes de depuración de la interfaz gráfica de usuario</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
|
||||||
<source>Enable wl-clipboard support</source>
|
|
||||||
<translation type="unfinished">Habilitar la compatibilidad con wl-clipboard</translation>
|
|
||||||
</message>
|
|
||||||
<message>
|
|
||||||
<source><html><head/><body><p>Requires the wl-clipboard package</p><p>When using wl-clipboard v2.2.1, there is a focus stealing bug that may make Deskflow harder to use. This has been fixed when using the wl-clipboard master branch, unless your Compositor lacks wlroots-data-control protocol support.</p></body></html></source>
|
|
||||||
<translation type="unfinished"><html><head/><body><p>Requiere el paquete wl-clipboard</p><p>Al usar wl-clipboard v2.2.1, existe un error que provoca la pérdida del foco y que puede dificultar el uso de Deskflow. Este error se ha corregido al usar la rama principal de wl-clipboard, a menos que su Compositor no sea compatible con el protocolo wlroots-data-control.</p></body></html></translation>
|
|
||||||
</message>
|
|
||||||
<message>
|
<message>
|
||||||
<source>Automatic</source>
|
<source>Automatic</source>
|
||||||
<translation type="unfinished">Automática</translation>
|
<translation type="unfinished">Automática</translation>
|
||||||
|
|
|
||||||
|
|
@ -1260,14 +1260,6 @@ L'abilitazione di questa impostazione disabiliterà l'interfaccia graf
|
||||||
<source>Enable GUI debug messages</source>
|
<source>Enable GUI debug messages</source>
|
||||||
<translation>Abilita i messaggi di debug della GUI</translation>
|
<translation>Abilita i messaggi di debug della GUI</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
|
||||||
<source>Enable wl-clipboard support</source>
|
|
||||||
<translation>Abilita il supporto wl-clipboard</translation>
|
|
||||||
</message>
|
|
||||||
<message>
|
|
||||||
<source><html><head/><body><p>Requires the wl-clipboard package</p><p>When using wl-clipboard v2.2.1, there is a focus stealing bug that may make Deskflow harder to use. This has been fixed when using the wl-clipboard master branch, unless your Compositor lacks wlroots-data-control protocol support.</p></body></html></source>
|
|
||||||
<translation><html><head/><body><p>Richiede il pacchetto wl-clipboard</p><p>Quando si utilizza wl-clipboard v2.2.1, si verifica un bug di furto del focus che potrebbe rendere Deskflow più difficile da usare. Questo problema è stato risolto quando si utilizza il ramo master di wl-clipboard, a meno che il proprio compositore non supporti il protocollo wlroots-data-control.</p></body></html></translation>
|
|
||||||
</message>
|
|
||||||
<message>
|
<message>
|
||||||
<source>Automatic</source>
|
<source>Automatic</source>
|
||||||
<translation type="unfinished">Automatica</translation>
|
<translation type="unfinished">Automatica</translation>
|
||||||
|
|
|
||||||
|
|
@ -1262,14 +1262,6 @@ Enabling this setting will disable the server config GUI.</source>
|
||||||
<source>Reset to default values</source>
|
<source>Reset to default values</source>
|
||||||
<translation>デフォルト値にリセットする</translation>
|
<translation>デフォルト値にリセットする</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
|
||||||
<source>Enable wl-clipboard support</source>
|
|
||||||
<translation>wl-clipboard によるクリップボード対応を有効にする</translation>
|
|
||||||
</message>
|
|
||||||
<message>
|
|
||||||
<source><html><head/><body><p>Requires the wl-clipboard package</p><p>When using wl-clipboard v2.2.1, there is a focus stealing bug that may make Deskflow harder to use. This has been fixed when using the wl-clipboard master branch, unless your Compositor lacks wlroots-data-control protocol support.</p></body></html></source>
|
|
||||||
<translation><html><head/><body><p>wl-clipboard パッケージが必要です。</p><p>wl-clipboard v2.2.1 を使用すると、フォーカス盗用のバグにより Deskflow の使い勝手が悪くなる可能性があります。この問題は wl-clipboard のマスターブランチで修正されていますが、使用しているコンポジターが wlroots-data-control プロトコルに対応している必要があります。</p></body></html></translation>
|
|
||||||
</message>
|
|
||||||
<message>
|
<message>
|
||||||
<source>Automatic</source>
|
<source>Automatic</source>
|
||||||
<translation>自動</translation>
|
<translation>自動</translation>
|
||||||
|
|
|
||||||
|
|
@ -1260,14 +1260,6 @@ Enabling this setting will disable the server config GUI.</source>
|
||||||
<source>Reset to default values</source>
|
<source>Reset to default values</source>
|
||||||
<translation type="unfinished">기본값으로 재설정</translation>
|
<translation type="unfinished">기본값으로 재설정</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
|
||||||
<source>Enable wl-clipboard support</source>
|
|
||||||
<translation>wl-clipboard 지원 사용</translation>
|
|
||||||
</message>
|
|
||||||
<message>
|
|
||||||
<source><html><head/><body><p>Requires the wl-clipboard package</p><p>When using wl-clipboard v2.2.1, there is a focus stealing bug that may make Deskflow harder to use. This has been fixed when using the wl-clipboard master branch, unless your Compositor lacks wlroots-data-control protocol support.</p></body></html></source>
|
|
||||||
<translation><html><head/><body><p>wl-clipboard 패키지가 필요합니다.</p><p>wl-clipboard v2.2.1 사용 시 포커스 탈취 버그로 인해 Deskflow 사용이 어려워질 수 있습니다. 이 문제는 wl-clipboard master 브랜치를 사용하면 해결되지만, 사용 중인 컴포지터가 wlroots-data-control 프로토콜을 지원하지 않으면 해결이 안 될 수 있습니다.</p></body></html></translation>
|
|
||||||
</message>
|
|
||||||
<message>
|
<message>
|
||||||
<source>Automatic</source>
|
<source>Automatic</source>
|
||||||
<translation type="unfinished">자동</translation>
|
<translation type="unfinished">자동</translation>
|
||||||
|
|
|
||||||
|
|
@ -1202,10 +1202,6 @@ Enabling this setting will disable the server config GUI.</source>
|
||||||
<source>Language</source>
|
<source>Language</source>
|
||||||
<translation>Язык</translation>
|
<translation>Язык</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
|
||||||
<source>Enable wl-clipboard support</source>
|
|
||||||
<translation>Включить поддержку wl-clipboard</translation>
|
|
||||||
</message>
|
|
||||||
<message>
|
<message>
|
||||||
<source>TLS Certificate Regenerated</source>
|
<source>TLS Certificate Regenerated</source>
|
||||||
<translation>Сертификат TLS перевыпущен</translation>
|
<translation>Сертификат TLS перевыпущен</translation>
|
||||||
|
|
@ -1262,10 +1258,6 @@ Enabling this setting will disable the server config GUI.</source>
|
||||||
<source>Reset to default values</source>
|
<source>Reset to default values</source>
|
||||||
<translation type="unfinished">Сбросить до значений по умолчанию</translation>
|
<translation type="unfinished">Сбросить до значений по умолчанию</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
|
||||||
<source><html><head/><body><p>Requires the wl-clipboard package</p><p>When using wl-clipboard v2.2.1, there is a focus stealing bug that may make Deskflow harder to use. This has been fixed when using the wl-clipboard master branch, unless your Compositor lacks wlroots-data-control protocol support.</p></body></html></source>
|
|
||||||
<translation><html><head/><body><p>Требуется пакет wl-clipboard. В версии 2.2.1 есть ошибка перехвата фокуса.</p></body></html></translation>
|
|
||||||
</message>
|
|
||||||
<message>
|
<message>
|
||||||
<source>Automatic</source>
|
<source>Automatic</source>
|
||||||
<translation>Автоматически</translation>
|
<translation>Автоматически</translation>
|
||||||
|
|
|
||||||
|
|
@ -1262,14 +1262,6 @@ Enabling this setting will disable the server config GUI.</source>
|
||||||
<source>Reset to default values</source>
|
<source>Reset to default values</source>
|
||||||
<translation type="unfinished">重置为默认值</translation>
|
<translation type="unfinished">重置为默认值</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
|
||||||
<source>Enable wl-clipboard support</source>
|
|
||||||
<translation>启用 wl-clipboard 支持</translation>
|
|
||||||
</message>
|
|
||||||
<message>
|
|
||||||
<source><html><head/><body><p>Requires the wl-clipboard package</p><p>When using wl-clipboard v2.2.1, there is a focus stealing bug that may make Deskflow harder to use. This has been fixed when using the wl-clipboard master branch, unless your Compositor lacks wlroots-data-control protocol support.</p></body></html></source>
|
|
||||||
<translation><html><head/><body><p>需要 wl-clipboard 包</p><p>使用 wl-clipboard v2.2.1 时存在一个焦点抢夺 Bug,可能导致 Deskflow 使用不便。该问题已在 wl-clipboard 的 master 分支中修复,除非您的合成器不支持 wlroots-data-control 协议。</p></body></html></translation>
|
|
||||||
</message>
|
|
||||||
<message>
|
<message>
|
||||||
<source>Automatic</source>
|
<source>Automatic</source>
|
||||||
<translation type="unfinished">自动的</translation>
|
<translation type="unfinished">自动的</translation>
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue