10 Commits

Author SHA1 Message Date
abaf233a07 fix: adjust command line size to allow expanding
Some checks are pending
CodeQL Analysis / codeql (push) Waiting to run
Continuous Integration / ci-passed (push) Blocked by required conditions
Continuous Integration / test-results (push) Blocked by required conditions
Continuous Integration / lint-reuse (push) Waiting to run
Continuous Integration / lint-clang (push) Blocked by required conditions
Continuous Integration / analyze-valgrind (push) Blocked by required conditions
Continuous Integration / windows-2022-x64 (push) Blocked by required conditions
Continuous Integration / windows-2022-arm64 (push) Blocked by required conditions
Continuous Integration / macos-arm64 (push) Blocked by required conditions
Continuous Integration / macos-x64 (push) Blocked by required conditions
Continuous Integration / archlinux-x86_64 (push) Blocked by required conditions
Continuous Integration / debian-arm64 (push) Blocked by required conditions
Continuous Integration / debian-x86_64 (push) Blocked by required conditions
Continuous Integration / debian-testing-arm64 (push) Blocked by required conditions
Continuous Integration / debian-testing-x86_64 (push) Blocked by required conditions
Continuous Integration / fedora-42-arm64 (push) Blocked by required conditions
Continuous Integration / fedora-42-x86_64 (push) Blocked by required conditions
Continuous Integration / fedora-43-arm64 (push) Blocked by required conditions
Continuous Integration / fedora-43-x86_64 (push) Blocked by required conditions
Continuous Integration / opensuse-arm64 (push) Blocked by required conditions
Continuous Integration / opensuse-x86_64 (push) Blocked by required conditions
Continuous Integration / ubuntu-25.10-arm64 (push) Blocked by required conditions
Continuous Integration / ubuntu-25.10-x86_64 (push) Blocked by required conditions
Continuous Integration / ubuntu-26.04-arm64 (push) Blocked by required conditions
Continuous Integration / ubuntu-26.04-x86_64 (push) Blocked by required conditions
Continuous Integration / unix-freebsd (push) Blocked by required conditions
Continuous Integration / flatpak-aarch64 (push) Blocked by required conditions
Continuous Integration / flatpak-x86_64 (push) Blocked by required conditions
Continuous Integration / release (push) Blocked by required conditions
SonarCloud Analysis / sonar (push) Waiting to run
2026-03-19 10:41:22 +00:00
b251c98a4e fix: kill core orphan on gui death (linux) 2026-03-16 09:38:14 -04:00
23c8496e24 feat: allow users to run a script on entry or exit of a screen 2026-03-16 12:33:45 +00:00
18220a7847 chore: Fix Typo 2026-03-16 12:33:45 +00:00
a26698c377 repo: add an initial code of conduct 2026-03-16 07:28:03 -04:00
ac8cc42b7a fix: retry OpenClipboard() on Windows
OpenClipboard() can fail transiently when another process holds the
clipboard mutex. Add a retry loop (5 attempts, 5ms delay) so that
Deskflow handles brief contention gracefully instead of immediately
failing.
2026-03-12 08:19:43 -04:00
7126f31d3f fix: do not reset network if old interface is missing
fixes: #9552
2026-03-12 10:24:20 +00:00
44db410dad docs: rm copilotnote 2026-03-11 07:48:51 -04:00
bc7b1082e7 doc: fix ssl exception link 2026-03-11 07:48:51 -04:00
ea0bf9d56d docs: Update README.md include Copilot note 2026-03-08 18:38:07 +00:00
16 changed files with 230 additions and 57 deletions

18
.github/CODE_OF_CONDUCT.md vendored Normal file
View File

@ -0,0 +1,18 @@
# Deskflow Code of Conduct
## Our Pledge
We want the Deskflow community to be one where everyone can work together. We pledge to keep our community focused on the project and the code around the project.
## Community Standards
* Keep interactions respectful and focused on the project
* Contributions are expected to follow the [contribution guide](https://github.com/deskflow/deskflow/wiki/Contributing).
## Enforcement
Enforcement will be done at the descression of the Deskflow Moderators.
1. Warning
2. Temporary ban
3. Permanent banning

2
.github/README.md vendored
View File

@ -184,4 +184,4 @@ Deskflow is made by possible by these contributors.
## License ## License
This project is licensed under [GPL-2.0](LICENSE) with an [OpenSSL exception](LICENSES/LicenseRef-OpenSSL-Exception.txt). This project is licensed under [GPL-2.0](LICENSE) with an [OpenSSL exception](../LICENSES/LicenseRef-OpenSSL-Exception.txt).

View File

@ -77,6 +77,10 @@ This section contains general options it will begin with `[core]`
| 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] | | 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] |
| 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] |
| exitCommand | command | A command to run when the screen is exited. |
### Daemon ### Daemon

View File

@ -55,6 +55,10 @@ public:
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 UseWlClipboard = QStringLiteral("core/wlClipboard");
inline static const auto EnableEnterCommand = QStringLiteral("core/enableEnterCommand");
inline static const auto ScreenEnterCommand = QStringLiteral("core/enterCommand");
inline static const auto EnableExitCommand = QStringLiteral("core/enableExitCommand");
inline static const auto ScreenExitCommand = QStringLiteral("core/exitCommand");
// TODO: REMOVE In 2.0 // TODO: REMOVE In 2.0
inline static const auto ScreenName = QStringLiteral("core/screenName"); // Replaced By ComputerName inline static const auto ScreenName = QStringLiteral("core/screenName"); // Replaced By ComputerName
@ -207,6 +211,10 @@ private:
, Settings::Core::Port , Settings::Core::Port
, Settings::Core::PreventSleep , Settings::Core::PreventSleep
, Settings::Core::ProcessMode , Settings::Core::ProcessMode
, Settings::Core::EnableEnterCommand
, Settings::Core::EnableExitCommand
, Settings::Core::ScreenEnterCommand
, Settings::Core::ScreenExitCommand
, Settings::Core::ScreenName , Settings::Core::ScreenName
, Settings::Core::ComputerName , Settings::Core::ComputerName
, Settings::Core::Display , Settings::Core::Display
@ -250,6 +258,8 @@ private:
, Settings::Gui::ShowVersionInTitle , Settings::Gui::ShowVersionInTitle
, Settings::Core::PreventSleep , Settings::Core::PreventSleep
, Settings::Core::UseWlClipboard , Settings::Core::UseWlClipboard
, Settings::Core::EnableEnterCommand
, Settings::Core::EnableExitCommand
, Settings::Server::ExternalConfig , Settings::Server::ExternalConfig
, Settings::Client::InvertYScroll , Settings::Client::InvertYScroll
, Settings::Client::InvertXScroll , Settings::Client::InvertXScroll

View File

@ -10,6 +10,8 @@
#include "base/Log.h" #include "base/Log.h"
#include "deskflow/IPlatformScreen.h" #include "deskflow/IPlatformScreen.h"
#include <QProcess>
namespace deskflow { namespace deskflow {
// //
@ -119,6 +121,14 @@ void Screen::enter(KeyModifierMask toggleMask)
} else { } else {
enterSecondary(toggleMask); enterSecondary(toggleMask);
} }
if (Settings::value(Settings::Core::EnableEnterCommand).toBool()) {
auto args = QProcess::splitCommand(Settings::value(Settings::Core::ScreenEnterCommand).toString());
const auto command = args.takeFirst();
LOG_DEBUG("running screen enter command: %s %s", qPrintable(command), qPrintable(args.join(" ")));
if (!QProcess::startDetached(command, args))
LOG_ERR("failed to run screen enter command");
}
} }
bool Screen::leave() bool Screen::leave()
@ -140,6 +150,13 @@ bool Screen::leave()
} }
m_screen->leave(); m_screen->leave();
if (Settings::value(Settings::Core::EnableExitCommand).toBool()) {
auto args = QProcess::splitCommand(Settings::value(Settings::Core::ScreenExitCommand).toString());
const auto command = args.takeFirst();
LOG_DEBUG("running screen exit command: %s %s", qPrintable(command), qPrintable(args.join(" ")));
if (!QProcess::startDetached(command, args))
LOG_ERR("failed to run screen exit command");
}
// make sure our idea of clipboard ownership is correct // make sure our idea of clipboard ownership is correct
m_screen->checkClipboards(); m_screen->checkClipboards();

View File

@ -14,6 +14,11 @@
#include "OSXHelpers.h" #include "OSXHelpers.h"
#endif #endif
#ifdef Q_OS_LINUX
#include <signal.h>
#include <sys/prctl.h>
#endif
#include <QCoreApplication> #include <QCoreApplication>
#include <QDir> #include <QDir>
#include <QFile> #include <QFile>
@ -203,6 +208,14 @@ void CoreProcess::startForegroundProcess(const QStringList &args)
const auto quoted = makeQuotedArgs(m_appPath, args); const auto quoted = makeQuotedArgs(m_appPath, args);
qInfo("running command: %s", qPrintable(quoted)); qInfo("running command: %s", qPrintable(quoted));
#ifdef Q_OS_LINUX
m_process->setChildProcessModifier([] {
// the core process becomes orphaned when the gui process exits abruptly (e.g. with kill -9),
// so ensure the os also kills the core when that happens to the gui.
prctl(PR_SET_PDEATHSIG, SIGTERM);
});
#endif
m_process->start(m_appPath, args); m_process->start(m_appPath, args);
if (m_process->waitForStarted()) { if (m_process->waitForStarted()) {

View File

@ -31,10 +31,9 @@ SettingsDialog::SettingsDialog(QWidget *parent, const IServerConfig &serverConfi
ui->setupUi(this); ui->setupUi(this);
// hide advanced options on macOS and portable windows // these are enabled by the control next to them
if (deskflow::platform::isMac() || (deskflow::platform::isWindows() && Settings::isPortableMode())) { ui->lineCommandEnter->setEnabled(false);
ui->tabWidget->removeTab(ui->tabWidget->indexOf(ui->tabAdvanced)); ui->lineCommandExit->setEnabled(false);
}
// set up the language combo // set up the language combo
I18N::reDetectLanguages(); I18N::reDetectLanguages();
@ -63,6 +62,11 @@ SettingsDialog::SettingsDialog(QWidget *parent, const IServerConfig &serverConfi
} }
} }
if (const auto interface = Settings::value(Settings::Core::Interface).toString();
!interface.isEmpty() && (ui->comboInterface->findData(interface) == -1)) {
ui->comboInterface->addItem(interface, interface);
}
loadFromConfig(); loadFromConfig();
adjustSize(); adjustSize();
@ -95,6 +99,9 @@ void SettingsDialog::initConnections() const
&SettingsDialog::resetToDefault &SettingsDialog::resetToDefault
); );
connect(ui->cbRunEnterCommand, &QCheckBox::toggled, ui->lineCommandEnter, &QLineEdit::setEnabled);
connect(ui->cbRunExitCommand, &QCheckBox::toggled, ui->lineCommandExit, &QLineEdit::setEnabled);
connect(ui->groupSecurity, &QGroupBox::toggled, this, &SettingsDialog::updateTlsControlsEnabled); connect(ui->groupSecurity, &QGroupBox::toggled, this, &SettingsDialog::updateTlsControlsEnabled);
connect(ui->groupService, &QGroupBox::toggled, this, &SettingsDialog::updateControls); connect(ui->groupService, &QGroupBox::toggled, this, &SettingsDialog::updateControls);
connect(ui->btnTlsRegenCert, &QPushButton::clicked, this, &SettingsDialog::regenCertificates); connect(ui->btnTlsRegenCert, &QPushButton::clicked, this, &SettingsDialog::regenCertificates);
@ -129,6 +136,10 @@ void SettingsDialog::initConnections() const
connect(ui->groupSecurity, &QGroupBox::toggled, this, &SettingsDialog::setButtonBoxEnabledButtons); connect(ui->groupSecurity, &QGroupBox::toggled, this, &SettingsDialog::setButtonBoxEnabledButtons);
connect(ui->lineLogFilename, &QLineEdit::textChanged, this, &SettingsDialog::setButtonBoxEnabledButtons); connect(ui->lineLogFilename, &QLineEdit::textChanged, this, &SettingsDialog::setButtonBoxEnabledButtons);
connect(ui->lineTlsCertPath, &QLineEdit::textChanged, this, &SettingsDialog::setButtonBoxEnabledButtons); connect(ui->lineTlsCertPath, &QLineEdit::textChanged, this, &SettingsDialog::setButtonBoxEnabledButtons);
connect(ui->cbRunEnterCommand, &QCheckBox::toggled, this, &SettingsDialog::setButtonBoxEnabledButtons);
connect(ui->cbRunExitCommand, &QCheckBox::toggled, this, &SettingsDialog::setButtonBoxEnabledButtons);
connect(ui->lineCommandEnter, &QLineEdit::textChanged, this, &SettingsDialog::setButtonBoxEnabledButtons);
connect(ui->lineCommandExit, &QLineEdit::textChanged, this, &SettingsDialog::setButtonBoxEnabledButtons);
} }
void SettingsDialog::regenCertificates() void SettingsDialog::regenCertificates()
@ -224,6 +235,10 @@ void SettingsDialog::accept()
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::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::EnableExitCommand, ui->cbRunExitCommand->isChecked());
Settings::setValue(Settings::Core::ScreenEnterCommand, ui->lineCommandEnter->text());
Settings::setValue(Settings::Core::ScreenExitCommand, ui->lineCommandExit->text());
Settings::ProcessMode mode; Settings::ProcessMode mode;
if (ui->groupService->isChecked()) if (ui->groupService->isChecked())
@ -247,6 +262,10 @@ void SettingsDialog::loadFromConfig()
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->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->cbRunExitCommand->setChecked(Settings::value(Settings::Core::EnableExitCommand).toBool());
ui->lineCommandEnter->setText(Settings::value(Settings::Core::ScreenEnterCommand).toString());
ui->lineCommandExit->setText(Settings::value(Settings::Core::ScreenExitCommand).toString());
const auto processMode = Settings::value(Settings::Core::ProcessMode).value<Settings::ProcessMode>(); const auto processMode = Settings::value(Settings::Core::ProcessMode).value<Settings::ProcessMode>();
ui->groupService->setChecked(processMode == Settings::ProcessMode::Service); ui->groupService->setChecked(processMode == Settings::ProcessMode::Service);
@ -356,6 +375,10 @@ void SettingsDialog::updateControls()
ui->comboTlsKeyLength->setEnabled(writable); ui->comboTlsKeyLength->setEnabled(writable);
ui->rbCloseToTray->setEnabled(writable); ui->rbCloseToTray->setEnabled(writable);
ui->rbExitOnClose->setEnabled(writable); ui->rbExitOnClose->setEnabled(writable);
ui->cbRunEnterCommand->setEnabled(writable);
ui->cbRunExitCommand->setEnabled(writable);
ui->lineCommandEnter->setEnabled(writable && ui->cbRunEnterCommand->isChecked());
ui->lineCommandExit->setEnabled(writable && ui->cbRunExitCommand->isChecked());
// Portable mode only ever applies to Windows. // Portable mode only ever applies to Windows.
// Daemon options should only be available on Windows when *not* in portable mode. // Daemon options should only be available on Windows when *not* in portable mode.
@ -415,6 +438,10 @@ bool SettingsDialog::isModified() const
(ui->comboTlsKeyLength->currentText() != Settings::value(Settings::Security::KeySize).toString()) || (ui->comboTlsKeyLength->currentText() != Settings::value(Settings::Security::KeySize).toString()) ||
(ui->groupSecurity->isChecked() != Settings::value(Settings::Security::TlsEnabled).toBool()) || (ui->groupSecurity->isChecked() != Settings::value(Settings::Security::TlsEnabled).toBool()) ||
(ui->cbRequireClientCert->isChecked() != Settings::value(Settings::Security::CheckPeers).toBool()) || (ui->cbRequireClientCert->isChecked() != Settings::value(Settings::Security::CheckPeers).toBool()) ||
(ui->cbRunEnterCommand->isChecked() != Settings::value(Settings::Core::EnableEnterCommand).toBool()) ||
(ui->cbRunExitCommand->isChecked() != Settings::value(Settings::Core::EnableExitCommand).toBool()) ||
(ui->lineCommandEnter->text() != Settings::value(Settings::Core::ScreenEnterCommand).toString()) ||
(ui->lineCommandExit->text() != Settings::value(Settings::Core::ScreenExitCommand).toString()) ||
(I18N::nativeTo639Name(ui->comboLanguage->currentText()) != Settings::value(Settings::Core::Language).toString()); (I18N::nativeTo639Name(ui->comboLanguage->currentText()) != Settings::value(Settings::Core::Language).toString());
if (!ignoreInterface) if (!ignoreInterface)
@ -446,6 +473,10 @@ bool SettingsDialog::isDefault() const
(ui->comboTlsKeyLength->currentText() == Settings::defaultValue(Settings::Security::KeySize).toString()) && (ui->comboTlsKeyLength->currentText() == Settings::defaultValue(Settings::Security::KeySize).toString()) &&
(ui->groupSecurity->isChecked() == Settings::defaultValue(Settings::Security::TlsEnabled).toBool()) && (ui->groupSecurity->isChecked() == Settings::defaultValue(Settings::Security::TlsEnabled).toBool()) &&
(ui->cbRequireClientCert->isChecked() == Settings::defaultValue(Settings::Security::CheckPeers).toBool()) && (ui->cbRequireClientCert->isChecked() == Settings::defaultValue(Settings::Security::CheckPeers).toBool()) &&
(ui->lineCommandEnter->text() == Settings::defaultValue(Settings::Core::ScreenEnterCommand).toString()) &&
(ui->lineCommandExit->text() == Settings::defaultValue(Settings::Core::ScreenExitCommand).toString()) &&
(ui->cbRunEnterCommand->isChecked() == Settings::defaultValue(Settings::Core::EnableEnterCommand).toBool()) &&
(ui->cbRunExitCommand->isChecked() == Settings::defaultValue(Settings::Core::EnableExitCommand).toBool()) &&
(ui->comboLanguage->currentText() == "English") (ui->comboLanguage->currentText() == "English")
); );
} }
@ -462,6 +493,10 @@ void SettingsDialog::resetToDefault()
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->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->cbRunExitCommand->setChecked(Settings::defaultValue(Settings::Core::EnableExitCommand).toBool());
ui->lineCommandEnter->setText(Settings::defaultValue(Settings::Core::ScreenEnterCommand).toString());
ui->lineCommandExit->setText(Settings::defaultValue(Settings::Core::ScreenExitCommand).toString());
const auto autoHide = Settings::defaultValue(Settings::Gui::Autohide).toBool(); const auto autoHide = Settings::defaultValue(Settings::Gui::Autohide).toBool();
ui->rbCloseToTray->setChecked(autoHide); ui->rbCloseToTray->setChecked(autoHide);

View File

@ -6,33 +6,30 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>564</width> <width>490</width>
<height>431</height> <height>366</height>
</rect> </rect>
</property> </property>
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Maximum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="windowTitle"> <property name="windowTitle">
<string>Preferences</string> <string>Preferences</string>
</property> </property>
<property name="modal"> <property name="modal">
<bool>true</bool> <bool>true</bool>
</property> </property>
<layout class="QVBoxLayout" name="_13"> <layout class="QVBoxLayout" name="verticalLayout_3">
<property name="spacing">
<number>20</number>
</property>
<item> <item>
<widget class="QTabWidget" name="tabWidget"> <widget class="QTabWidget" name="tabWidget">
<widget class="QWidget" name="tabGeneral"> <widget class="QWidget" name="tabGeneral">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<attribute name="title"> <attribute name="title">
<string>&amp;General</string> <string>&amp;General</string>
</attribute> </attribute>
<layout class="QVBoxLayout" name="verticalLayout_3"> <layout class="QVBoxLayout" name="verticalLayout_6">
<property name="spacing"> <property name="spacing">
<number>9</number> <number>9</number>
</property> </property>
@ -40,13 +37,13 @@
<number>12</number> <number>12</number>
</property> </property>
<property name="topMargin"> <property name="topMargin">
<number>20</number> <number>0</number>
</property> </property>
<property name="rightMargin"> <property name="rightMargin">
<number>12</number> <number>12</number>
</property> </property>
<property name="bottomMargin"> <property name="bottomMargin">
<number>20</number> <number>0</number>
</property> </property>
<item> <item>
<spacer name="verticalSpacer_6"> <spacer name="verticalSpacer_6">
@ -147,16 +144,16 @@
<number>12</number> <number>12</number>
</property> </property>
<property name="topMargin"> <property name="topMargin">
<number>20</number> <number>0</number>
</property> </property>
<property name="rightMargin"> <property name="rightMargin">
<number>12</number> <number>12</number>
</property> </property>
<property name="bottomMargin"> <property name="bottomMargin">
<number>20</number> <number>0</number>
</property> </property>
<item> <item>
<spacer name="verticalSpacer_10"> <spacer name="verticalSpacer_7">
<property name="orientation"> <property name="orientation">
<enum>Qt::Orientation::Vertical</enum> <enum>Qt::Orientation::Vertical</enum>
</property> </property>
@ -266,16 +263,16 @@
<number>12</number> <number>12</number>
</property> </property>
<property name="topMargin"> <property name="topMargin">
<number>20</number> <number>0</number>
</property> </property>
<property name="rightMargin"> <property name="rightMargin">
<number>12</number> <number>12</number>
</property> </property>
<property name="bottomMargin"> <property name="bottomMargin">
<number>20</number> <number>0</number>
</property> </property>
<item> <item>
<spacer name="verticalSpacer_7"> <spacer name="verticalSpacer_8">
<property name="orientation"> <property name="orientation">
<enum>Qt::Orientation::Vertical</enum> <enum>Qt::Orientation::Vertical</enum>
</property> </property>
@ -492,16 +489,16 @@
<number>12</number> <number>12</number>
</property> </property>
<property name="topMargin"> <property name="topMargin">
<number>20</number> <number>0</number>
</property> </property>
<property name="rightMargin"> <property name="rightMargin">
<number>12</number> <number>12</number>
</property> </property>
<property name="bottomMargin"> <property name="bottomMargin">
<number>20</number> <number>0</number>
</property> </property>
<item> <item>
<spacer name="verticalSpacer_8"> <spacer name="verticalSpacer_9">
<property name="orientation"> <property name="orientation">
<enum>Qt::Orientation::Vertical</enum> <enum>Qt::Orientation::Vertical</enum>
</property> </property>
@ -788,12 +785,6 @@
</layout> </layout>
</widget> </widget>
<widget class="QWidget" name="tabAdvanced"> <widget class="QWidget" name="tabAdvanced">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<attribute name="title"> <attribute name="title">
<string>&amp;Advanced</string> <string>&amp;Advanced</string>
</attribute> </attribute>
@ -805,16 +796,16 @@
<number>12</number> <number>12</number>
</property> </property>
<property name="topMargin"> <property name="topMargin">
<number>20</number> <number>0</number>
</property> </property>
<property name="rightMargin"> <property name="rightMargin">
<number>12</number> <number>12</number>
</property> </property>
<property name="bottomMargin"> <property name="bottomMargin">
<number>20</number> <number>0</number>
</property> </property>
<item> <item>
<spacer name="verticalSpacer_9"> <spacer name="verticalSpacer_10">
<property name="orientation"> <property name="orientation">
<enum>Qt::Orientation::Vertical</enum> <enum>Qt::Orientation::Vertical</enum>
</property> </property>
@ -831,12 +822,6 @@
<property name="enabled"> <property name="enabled">
<bool>true</bool> <bool>true</bool>
</property> </property>
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Maximum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="title"> <property name="title">
<string>Use background service (daemon)</string> <string>Use background service (daemon)</string>
</property> </property>
@ -862,12 +847,6 @@
</item> </item>
<item> <item>
<widget class="QWidget" name="widgetWlClipboard" native="true"> <widget class="QWidget" name="widgetWlClipboard" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<layout class="QVBoxLayout" name="verticalLayout_5"> <layout class="QVBoxLayout" name="verticalLayout_5">
<item> <item>
<widget class="QCheckBox" name="cbUseWlClipboard"> <widget class="QCheckBox" name="cbUseWlClipboard">
@ -889,6 +868,42 @@
</layout> </layout>
</widget> </widget>
</item> </item>
<item>
<layout class="QFormLayout" name="formLayout_3">
<property name="fieldGrowthPolicy">
<enum>QFormLayout::FieldGrowthPolicy::ExpandingFieldsGrow</enum>
</property>
<property name="rowWrapPolicy">
<enum>QFormLayout::RowWrapPolicy::DontWrapRows</enum>
</property>
<property name="labelAlignment">
<set>Qt::AlignmentFlag::AlignLeading|Qt::AlignmentFlag::AlignLeft|Qt::AlignmentFlag::AlignVCenter</set>
</property>
<property name="formAlignment">
<set>Qt::AlignmentFlag::AlignHCenter|Qt::AlignmentFlag::AlignTop</set>
</property>
<item row="0" column="0">
<widget class="QCheckBox" name="cbRunEnterCommand">
<property name="text">
<string>Run command on enter</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="lineCommandEnter"/>
</item>
<item row="1" column="0">
<widget class="QCheckBox" name="cbRunExitCommand">
<property name="text">
<string>Run command on exit</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLineEdit" name="lineCommandExit"/>
</item>
</layout>
</item>
<item> <item>
<spacer name="verticalSpacer"> <spacer name="verticalSpacer">
<property name="orientation"> <property name="orientation">

View File

@ -1,5 +1,6 @@
/* /*
* Deskflow -- mouse and keyboard sharing utility * Deskflow -- mouse and keyboard sharing utility
* SPDX-FileCopyrightText: (C) 2026 Deskflow Developers
* SPDX-FileCopyrightText: (C) 2012 - 2016 Symless Ltd. * SPDX-FileCopyrightText: (C) 2012 - 2016 Symless Ltd.
* SPDX-FileCopyrightText: (C) 2002 Chris Schoeneman * SPDX-FileCopyrightText: (C) 2002 Chris Schoeneman
* SPDX-License-Identifier: GPL-2.0-only WITH LicenseRef-OpenSSL-Exception * SPDX-License-Identifier: GPL-2.0-only WITH LicenseRef-OpenSSL-Exception
@ -116,14 +117,26 @@ bool MSWindowsClipboard::open(Time time) const
{ {
LOG_DEBUG("open clipboard"); LOG_DEBUG("open clipboard");
if (!OpenClipboard(m_window)) { // The clipboard is a global mutex on Windows. We aren't always going to
LOG_WARN("failed to open clipboard: %d", GetLastError()); // get the lock on the first try, so try a few times before giving up.
return false; // Based on Chromium's ScopedClipboard::Acquire() retry loop.
static const int kMaxRetries = 5;
static const int kRetryDelayMs = 5;
for (int i = 0; i < kMaxRetries; ++i) {
if (OpenClipboard(m_window)) {
m_time = time;
return true;
}
if (i < kMaxRetries - 1) {
LOG_DEBUG("failed to open clipboard (attempt %d/%d, error=%d), retrying", i + 1, kMaxRetries, GetLastError());
Sleep(kRetryDelayMs);
}
} }
m_time = time; LOG_WARN("failed to open clipboard after %d attempts: %d", kMaxRetries, GetLastError());
return false;
return true;
} }
void MSWindowsClipboard::close() const void MSWindowsClipboard::close() const

View File

@ -199,7 +199,7 @@ public:
//! Add screen //! Add screen
/*! /*!
Adds a screen, returning true iff successful. If a screen or Adds a screen, returning true if successful. If a screen or
alias with the given name exists then it fails. alias with the given name exists then it fails.
*/ */
bool addScreen(const std::string &name); bool addScreen(const std::string &name);

View File

@ -1309,6 +1309,14 @@ Al habilitar esta opción, se deshabilitará la interfaz gráfica de usuario (GU
<source>Show the main window</source> <source>Show the main window</source>
<translation type="unfinished">Mostrar la ventana principal</translation> <translation type="unfinished">Mostrar la ventana principal</translation>
</message> </message>
<message>
<source>Run command on enter</source>
<translation type="unfinished">Ejecutar comando al presionar Enter</translation>
</message>
<message>
<source>Run command on exit</source>
<translation type="unfinished">Ejecutar comando al salir</translation>
</message>
</context> </context>
<context> <context>
<name>StatusBar</name> <name>StatusBar</name>

View File

@ -1309,6 +1309,14 @@ L&apos;abilitazione di questa impostazione disabiliterà l&apos;interfaccia graf
<source>Show the main window</source> <source>Show the main window</source>
<translation type="unfinished">Mostra la finestra principale</translation> <translation type="unfinished">Mostra la finestra principale</translation>
</message> </message>
<message>
<source>Run command on enter</source>
<translation type="unfinished">Esegui il comando alla pressione di Invio</translation>
</message>
<message>
<source>Run command on exit</source>
<translation type="unfinished">Esegui comando all&apos;uscita</translation>
</message>
</context> </context>
<context> <context>
<name>StatusBar</name> <name>StatusBar</name>

View File

@ -1311,6 +1311,14 @@ Enabling this setting will disable the server config GUI.</source>
<source>Show the main window</source> <source>Show the main window</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message>
<source>Run command on enter</source>
<translation type="unfinished">Enterキーでコマンドを実行</translation>
</message>
<message>
<source>Run command on exit</source>
<translation type="unfinished"></translation>
</message>
</context> </context>
<context> <context>
<name>StatusBar</name> <name>StatusBar</name>

View File

@ -1309,6 +1309,14 @@ Enabling this setting will disable the server config GUI.</source>
<source>Show the main window</source> <source>Show the main window</source>
<translation type="unfinished"> </translation> <translation type="unfinished"> </translation>
</message> </message>
<message>
<source>Run command on enter</source>
<translation type="unfinished">Enter </translation>
</message>
<message>
<source>Run command on exit</source>
<translation type="unfinished"> </translation>
</message>
</context> </context>
<context> <context>
<name>StatusBar</name> <name>StatusBar</name>

View File

@ -1307,6 +1307,14 @@ Enabling this setting will disable the server config GUI.</source>
<source>Show the main window</source> <source>Show the main window</source>
<translation type="unfinished">Показать главное окно</translation> <translation type="unfinished">Показать главное окно</translation>
</message> </message>
<message>
<source>Run command on enter</source>
<translation type="unfinished">Выполнять команду по нажатию Enter</translation>
</message>
<message>
<source>Run command on exit</source>
<translation type="unfinished">Выполнить команду при выходе</translation>
</message>
</context> </context>
<context> <context>
<name>StatusBar</name> <name>StatusBar</name>

View File

@ -1311,6 +1311,14 @@ Enabling this setting will disable the server config GUI.</source>
<source>Show the main window</source> <source>Show the main window</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message>
<source>Run command on enter</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Run command on exit</source>
<translation type="unfinished">退</translation>
</message>
</context> </context>
<context> <context>
<name>StatusBar</name> <name>StatusBar</name>