feat: allow users to run a script on entry or exit of a screen

This commit is contained in:
sithlord48
2026-03-12 00:58:53 -04:00
committed by Nick Bolton
parent 18220a7847
commit 23c8496e24
11 changed files with 170 additions and 49 deletions

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] |
| 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] |
| 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

View File

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

View File

@ -10,6 +10,8 @@
#include "base/Log.h"
#include "deskflow/IPlatformScreen.h"
#include <QProcess>
namespace deskflow {
//
@ -119,6 +121,14 @@ void Screen::enter(KeyModifierMask toggleMask)
} else {
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()
@ -140,6 +150,13 @@ bool 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
m_screen->checkClipboards();

View File

@ -31,10 +31,9 @@ SettingsDialog::SettingsDialog(QWidget *parent, const IServerConfig &serverConfi
ui->setupUi(this);
// hide advanced options on macOS and portable windows
if (deskflow::platform::isMac() || (deskflow::platform::isWindows() && Settings::isPortableMode())) {
ui->tabWidget->removeTab(ui->tabWidget->indexOf(ui->tabAdvanced));
}
// these are enabled by the control next to them
ui->lineCommandEnter->setEnabled(false);
ui->lineCommandExit->setEnabled(false);
// set up the language combo
I18N::reDetectLanguages();
@ -100,6 +99,9 @@ void SettingsDialog::initConnections() const
&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->groupService, &QGroupBox::toggled, this, &SettingsDialog::updateControls);
connect(ui->btnTlsRegenCert, &QPushButton::clicked, this, &SettingsDialog::regenCertificates);
@ -134,6 +136,10 @@ void SettingsDialog::initConnections() const
connect(ui->groupSecurity, &QGroupBox::toggled, this, &SettingsDialog::setButtonBoxEnabledButtons);
connect(ui->lineLogFilename, &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()
@ -229,6 +235,10 @@ void SettingsDialog::accept()
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::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;
if (ui->groupService->isChecked())
@ -252,6 +262,10 @@ void SettingsDialog::loadFromConfig()
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->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>();
ui->groupService->setChecked(processMode == Settings::ProcessMode::Service);
@ -361,6 +375,10 @@ void SettingsDialog::updateControls()
ui->comboTlsKeyLength->setEnabled(writable);
ui->rbCloseToTray->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.
// Daemon options should only be available on Windows when *not* in portable mode.
@ -420,6 +438,10 @@ bool SettingsDialog::isModified() const
(ui->comboTlsKeyLength->currentText() != Settings::value(Settings::Security::KeySize).toString()) ||
(ui->groupSecurity->isChecked() != Settings::value(Settings::Security::TlsEnabled).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());
if (!ignoreInterface)
@ -451,6 +473,10 @@ bool SettingsDialog::isDefault() const
(ui->comboTlsKeyLength->currentText() == Settings::defaultValue(Settings::Security::KeySize).toString()) &&
(ui->groupSecurity->isChecked() == Settings::defaultValue(Settings::Security::TlsEnabled).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")
);
}
@ -467,6 +493,10 @@ void SettingsDialog::resetToDefault()
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->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();
ui->rbCloseToTray->setChecked(autoHide);

View File

@ -6,33 +6,30 @@
<rect>
<x>0</x>
<y>0</y>
<width>564</width>
<height>431</height>
<width>490</width>
<height>366</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Maximum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="windowTitle">
<string>Preferences</string>
</property>
<property name="modal">
<bool>true</bool>
</property>
<layout class="QVBoxLayout" name="_13">
<property name="spacing">
<number>20</number>
</property>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<widget class="QTabWidget" name="tabWidget">
<widget class="QWidget" name="tabGeneral">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<attribute name="title">
<string>&amp;General</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_3">
<layout class="QVBoxLayout" name="verticalLayout_6">
<property name="spacing">
<number>9</number>
</property>
@ -40,13 +37,13 @@
<number>12</number>
</property>
<property name="topMargin">
<number>20</number>
<number>0</number>
</property>
<property name="rightMargin">
<number>12</number>
</property>
<property name="bottomMargin">
<number>20</number>
<number>0</number>
</property>
<item>
<spacer name="verticalSpacer_6">
@ -147,16 +144,16 @@
<number>12</number>
</property>
<property name="topMargin">
<number>20</number>
<number>0</number>
</property>
<property name="rightMargin">
<number>12</number>
</property>
<property name="bottomMargin">
<number>20</number>
<number>0</number>
</property>
<item>
<spacer name="verticalSpacer_10">
<spacer name="verticalSpacer_7">
<property name="orientation">
<enum>Qt::Orientation::Vertical</enum>
</property>
@ -266,16 +263,16 @@
<number>12</number>
</property>
<property name="topMargin">
<number>20</number>
<number>0</number>
</property>
<property name="rightMargin">
<number>12</number>
</property>
<property name="bottomMargin">
<number>20</number>
<number>0</number>
</property>
<item>
<spacer name="verticalSpacer_7">
<spacer name="verticalSpacer_8">
<property name="orientation">
<enum>Qt::Orientation::Vertical</enum>
</property>
@ -492,16 +489,16 @@
<number>12</number>
</property>
<property name="topMargin">
<number>20</number>
<number>0</number>
</property>
<property name="rightMargin">
<number>12</number>
</property>
<property name="bottomMargin">
<number>20</number>
<number>0</number>
</property>
<item>
<spacer name="verticalSpacer_8">
<spacer name="verticalSpacer_9">
<property name="orientation">
<enum>Qt::Orientation::Vertical</enum>
</property>
@ -788,12 +785,6 @@
</layout>
</widget>
<widget class="QWidget" name="tabAdvanced">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<attribute name="title">
<string>&amp;Advanced</string>
</attribute>
@ -805,16 +796,16 @@
<number>12</number>
</property>
<property name="topMargin">
<number>20</number>
<number>0</number>
</property>
<property name="rightMargin">
<number>12</number>
</property>
<property name="bottomMargin">
<number>20</number>
<number>0</number>
</property>
<item>
<spacer name="verticalSpacer_9">
<spacer name="verticalSpacer_10">
<property name="orientation">
<enum>Qt::Orientation::Vertical</enum>
</property>
@ -831,12 +822,6 @@
<property name="enabled">
<bool>true</bool>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Maximum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="title">
<string>Use background service (daemon)</string>
</property>
@ -862,12 +847,6 @@
</item>
<item>
<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">
<item>
<widget class="QCheckBox" name="cbUseWlClipboard">
@ -889,6 +868,39 @@
</layout>
</widget>
</item>
<item>
<layout class="QFormLayout" name="formLayout_3">
<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>
<spacer name="verticalSpacer">
<property name="orientation">

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>
<translation type="unfinished">Mostrar la ventana principal</translation>
</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>
<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>
<translation type="unfinished">Mostra la finestra principale</translation>
</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>
<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>
<translation type="unfinished"></translation>
</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>
<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>
<translation type="unfinished"> </translation>
</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>
<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>
<translation type="unfinished">Показать главное окно</translation>
</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>
<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>
<translation type="unfinished"></translation>
</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>
<name>StatusBar</name>