Improve auto start setting
* Do not blindly override existing auto start entry * Warn if an auto start entry exists but points to a different executable * Allow removing such an entry explicitly
This commit is contained in:
parent
e703ddd524
commit
878e973c9d
|
@ -12,14 +12,14 @@
|
|||
<string>Autostart</string>
|
||||
</property>
|
||||
<property name="windowIcon">
|
||||
<iconset theme="preferences-system-startup"/>
|
||||
<iconset theme="preferences-system-startup">
|
||||
<normaloff>.</normaloff>.</iconset>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QCheckBox" name="autostartCheckBox">
|
||||
<property name="font">
|
||||
<font>
|
||||
<weight>75</weight>
|
||||
<bold>true</bold>
|
||||
</font>
|
||||
</property>
|
||||
|
@ -28,6 +28,61 @@
|
|||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QWidget" name="pathWidget" native="true">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QLabel" name="pathWarningIconLabel">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>32</width>
|
||||
<height>32</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>32</width>
|
||||
<height>32</height>
|
||||
</size>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="pathWarningLabel">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="deleteExistingEntryPushButton">
|
||||
<property name="toolTip">
|
||||
<string>Delete existing entry</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset theme="edit-delete" resource="../../tray/resources/syncthingtrayicons.qrc">
|
||||
<normaloff>:/icons/hicolor/scalable/actions/edit-delete.svg</normaloff>:/icons/hicolor/scalable/actions/edit-delete.svg</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
|
@ -63,6 +118,8 @@
|
|||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<resources>
|
||||
<include location="../../tray/resources/syncthingtrayicons.qrc"/>
|
||||
</resources>
|
||||
<connections/>
|
||||
</ui>
|
||||
|
|
|
@ -57,6 +57,8 @@
|
|||
#include <QStandardPaths>
|
||||
#elif defined(PLATFORM_WINDOWS)
|
||||
#include <QSettings>
|
||||
#include <QRegularExpression>
|
||||
#include <QRegularExpressionMatch>
|
||||
#elif defined(PLATFORM_MAC)
|
||||
#include <QFileInfo>
|
||||
#endif
|
||||
|
@ -739,9 +741,16 @@ AutostartOptionPage::~AutostartOptionPage()
|
|||
QWidget *AutostartOptionPage::setupWidget()
|
||||
{
|
||||
auto *widget = AutostartOptionPageBase::setupWidget();
|
||||
auto *style = QApplication::style();
|
||||
|
||||
ui()->infoIconLabel->setPixmap(
|
||||
QApplication::style()->standardIcon(QStyle::SP_MessageBoxInformation, nullptr, ui()->infoIconLabel).pixmap(ui()->infoIconLabel->size()));
|
||||
style->standardIcon(QStyle::SP_MessageBoxInformation, nullptr, ui()->infoIconLabel).pixmap(ui()->infoIconLabel->size()));
|
||||
ui()->pathWarningIconLabel->setPixmap(
|
||||
style->standardIcon(QStyle::SP_MessageBoxWarning, nullptr, ui()->pathWarningIconLabel).pixmap(ui()->pathWarningIconLabel->size()));
|
||||
QObject::connect(ui()->deleteExistingEntryPushButton, &QPushButton::clicked, widget, [this] {
|
||||
setAutostartPath(QString());
|
||||
reset();
|
||||
});
|
||||
#if defined(PLATFORM_LINUX) && !defined(PLATFORM_ANDROID)
|
||||
ui()->platformNoteLabel->setText(QCoreApplication::translate("QtGui::AutostartOptionPage",
|
||||
"This is achieved by adding a *.desktop file under <i>~/.config/autostart</i> so the setting only affects the current user."));
|
||||
|
@ -755,16 +764,143 @@ QWidget *AutostartOptionPage::setupWidget()
|
|||
#else
|
||||
ui()->platformNoteLabel->setText(
|
||||
QCoreApplication::translate("QtGui::AutostartOptionPage", "This feature has not been implemented for your platform (yet)."));
|
||||
m_unsupported = true;
|
||||
ui()->pathWidget->setVisible(false);
|
||||
ui()->autostartCheckBox->setEnabled(false);
|
||||
#endif
|
||||
return widget;
|
||||
}
|
||||
|
||||
std::optional<QString> configuredAutostartPath()
|
||||
{
|
||||
#if defined(PLATFORM_LINUX) && !defined(Q_OS_ANDROID)
|
||||
auto desktopFile = QFile(QStandardPaths::locate(QStandardPaths::ConfigLocation, QStringLiteral("autostart/" PROJECT_NAME ".desktop")));
|
||||
// check whether the file can be opened and whether it is enabled but prevent reading large files
|
||||
if (!desktopFile.open(QFile::ReadOnly)) {
|
||||
return QString();
|
||||
}
|
||||
if (desktopFile.size() > (5 * 1024)) {
|
||||
return std::nullopt;
|
||||
}
|
||||
const auto data = QString::fromUtf8(desktopFile.readAll());
|
||||
if (data.contains(QLatin1String("Hidden=true"))) {
|
||||
return QString();
|
||||
}
|
||||
static const auto regex = QRegularExpression(QStringLiteral("Exec=\"?([^\"]*)\"?"));
|
||||
const auto match = regex.match(data);
|
||||
return match.hasCaptured(1) ? std::make_optional(match.captured(1)) : std::nullopt;
|
||||
#elif defined(PLATFORM_WINDOWS)
|
||||
return QSettings(QStringLiteral("HKEY_CURRENT_USER\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run"), QSettings::NativeFormat)
|
||||
.value(QStringLiteral(PROJECT_NAME)).toString();
|
||||
#else
|
||||
return std::nullopt;
|
||||
#endif
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Returns the autostart path that will be configured by invoking setAutostartEnabled(true).
|
||||
* \remarks
|
||||
* - Only implemented under Linux/Windows/Mac. Always returns false on other platforms.
|
||||
* - Does not check whether the startup entry is functional (eg. the specified path is still valid and points to the
|
||||
* currently running instance of the application).
|
||||
*/
|
||||
QString supposedAutostartPath()
|
||||
{
|
||||
#if 1 || defined(PLATFORM_LINUX) && !defined(Q_OS_ANDROID)
|
||||
#ifndef SYNCTHINGWIDGETS_AUTOSTART_EXEC_PATH
|
||||
#define SYNCTHINGWIDGETS_AUTOSTART_EXEC_PATH QCoreApplication::applicationFilePath()
|
||||
#endif
|
||||
return qEnvironmentVariable("APPIMAGE", SYNCTHINGWIDGETS_AUTOSTART_EXEC_PATH);
|
||||
#elif defined(PLATFORM_WINDOWS)
|
||||
return QCoreApplication::applicationFilePath().replace(QChar('/'), QChar('\\');
|
||||
#else
|
||||
return QCoreApplication::applicationFilePath();
|
||||
#endif
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Sets the \a path of the application's autostart entry or removes the entry if \a path is empty.
|
||||
*/
|
||||
bool setAutostartPath(const QString &path)
|
||||
{
|
||||
#if defined(PLATFORM_LINUX) && !defined(Q_OS_ANDROID)
|
||||
const auto configPath = QStandardPaths::writableLocation(QStandardPaths::ConfigLocation);
|
||||
if (configPath.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
if (!path.isEmpty() && !QDir().mkpath(configPath + QStringLiteral("/autostart"))) {
|
||||
return false;
|
||||
}
|
||||
auto desktopFile = QFile(configPath + QStringLiteral("/autostart/" PROJECT_NAME ".desktop"));
|
||||
if (!path.isEmpty()) {
|
||||
if (!desktopFile.open(QFile::WriteOnly | QFile::Truncate)) {
|
||||
return false;
|
||||
}
|
||||
desktopFile.write("[Desktop Entry]\n"
|
||||
"Name=" APP_NAME "\n"
|
||||
"Exec=\"");
|
||||
desktopFile.write(path.toUtf8());
|
||||
desktopFile.write("\" qt-widgets-gui --single-instance\nComment=" APP_DESCRIPTION "\n"
|
||||
"Icon=" PROJECT_NAME "\n"
|
||||
"Type=Application\n"
|
||||
"Terminal=false\n"
|
||||
"X-GNOME-Autostart-Delay=0\n"
|
||||
"X-GNOME-Autostart-enabled=true");
|
||||
return desktopFile.error() == QFile::NoError && desktopFile.flush();
|
||||
|
||||
} else {
|
||||
return !desktopFile.exists() || desktopFile.remove();
|
||||
}
|
||||
|
||||
#elif defined(PLATFORM_WINDOWS)
|
||||
auto settings = QSettings(QStringLiteral("HKEY_CURRENT_USER\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run"), QSettings::NativeFormat);
|
||||
if (!path.isEmpty()) {
|
||||
settings.setValue(QStringLiteral(PROJECT_NAME), path);
|
||||
} else {
|
||||
settings.remove(QStringLiteral(PROJECT_NAME));
|
||||
}
|
||||
settings.sync();
|
||||
return true;
|
||||
|
||||
#elif defined(PLATFORM_MAC)
|
||||
const auto libraryPath = QDir::home().filePath(QStringLiteral("Library"));
|
||||
if (!path.isEmpty() && !QDir().mkpath(libraryPath + QStringLiteral("/LaunchAgents"))) {
|
||||
return false;
|
||||
}
|
||||
auto launchdPlistFile = QFile(libraryPath + QStringLiteral("/LaunchAgents/" PROJECT_NAME ".plist"));
|
||||
if (!path.isEmpty()) {
|
||||
if (!launchdPlistFile.open(QFile::WriteOnly | QFile::Truncate)) {
|
||||
return false;
|
||||
}
|
||||
launchdPlistFile.write("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
|
||||
"<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n"
|
||||
"<plist version=\"1.0\">\n"
|
||||
" <dict>\n"
|
||||
" <key>Label</key>\n"
|
||||
" <string>" PROJECT_NAME "</string>\n"
|
||||
" <key>ProgramArguments</key>\n"
|
||||
" <array>\n"
|
||||
" <string>");
|
||||
launchdPlistFile.write(path.toUtf8());
|
||||
launchdPlistFile.write("</string>\n"
|
||||
" </array>\n"
|
||||
" <key>KeepAlive</key>\n"
|
||||
" <true/>\n"
|
||||
" </dict>\n"
|
||||
"</plist>\n");
|
||||
return launchdPlistFile.error() == QFile::NoError && launchdPlistFile.flush();
|
||||
} else {
|
||||
return !launchdPlistFile.exists() || launchdPlistFile.remove();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Returns whether the application is launched on startup.
|
||||
* \remarks
|
||||
* - Only implemented under Linux/Windows. Always returns false on other platforms.
|
||||
* - Does not check whether the startup entry is functional (eg. the specified path is still valid).
|
||||
* - Only implemented under Linux/Windows/Mac. Always returns false on other platforms.
|
||||
* - Does not check whether the startup entry is functional (eg. the specified path is still valid and points to the
|
||||
* currently running instance of the application).
|
||||
*/
|
||||
bool isAutostartEnabled()
|
||||
{
|
||||
|
@ -786,96 +922,32 @@ bool isAutostartEnabled()
|
|||
}
|
||||
|
||||
/*!
|
||||
* \brief Sets whether the application is launchedc on startup.
|
||||
* \brief Sets whether the application is launched on startup.
|
||||
* \remarks
|
||||
* - Only implemented under Linux/Windows. Does nothing on other platforms.
|
||||
* - If a startup entry already exists and \a enabled is true, this function will ensure the path of the existing entry is valid.
|
||||
* - Only implemented under Linux/Windows/Mac. Does nothing on other platforms.
|
||||
* - If a startup entry already exists and \a enabled is true, this function will not touch the existing entry - even if it points
|
||||
* to another application. Delete the existing entry first if it is no longer wanted. If the currently configured path cannot be
|
||||
* determined it will always be overridden, though.
|
||||
* - If no startup entry could be detected via isAutostartEnabled() and \a enabled is false this function doesn't touch anything.
|
||||
*/
|
||||
bool setAutostartEnabled(bool enabled)
|
||||
{
|
||||
if (!isAutostartEnabled() && !enabled) {
|
||||
const auto configuredPath = configuredAutostartPath();
|
||||
if (!(configuredPath.has_value() ? !configuredPath.value().isEmpty() : isAutostartEnabled()) && !enabled) {
|
||||
return true;
|
||||
}
|
||||
|
||||
#if defined(PLATFORM_LINUX) && !defined(Q_OS_ANDROID)
|
||||
const auto configPath = QStandardPaths::writableLocation(QStandardPaths::ConfigLocation);
|
||||
if (configPath.isEmpty()) {
|
||||
return !enabled;
|
||||
const auto supposedPath = supposedAutostartPath();
|
||||
if (enabled && configuredPath.has_value() && !configuredPath.value().isEmpty() && configuredPath.value() != supposedPath) {
|
||||
return true; // don't touch existing entry
|
||||
}
|
||||
if (enabled && !QDir().mkpath(configPath + QStringLiteral("/autostart"))) {
|
||||
return false;
|
||||
}
|
||||
auto desktopFile = QFile(configPath + QStringLiteral("/autostart/" PROJECT_NAME ".desktop"));
|
||||
if (enabled) {
|
||||
if (!desktopFile.open(QFile::WriteOnly | QFile::Truncate)) {
|
||||
return false;
|
||||
}
|
||||
desktopFile.write("[Desktop Entry]\n"
|
||||
"Name=" APP_NAME "\n"
|
||||
"Exec=\"");
|
||||
#ifndef SYNCTHINGWIDGETS_AUTOSTART_EXEC_PATH
|
||||
#define SYNCTHINGWIDGETS_AUTOSTART_EXEC_PATH QCoreApplication::applicationFilePath()
|
||||
#endif
|
||||
desktopFile.write(qEnvironmentVariable("APPIMAGE", SYNCTHINGWIDGETS_AUTOSTART_EXEC_PATH).toUtf8().data());
|
||||
desktopFile.write("\" qt-widgets-gui --single-instance\nComment=" APP_DESCRIPTION "\n"
|
||||
"Icon=" PROJECT_NAME "\n"
|
||||
"Type=Application\n"
|
||||
"Terminal=false\n"
|
||||
"X-GNOME-Autostart-Delay=0\n"
|
||||
"X-GNOME-Autostart-enabled=true");
|
||||
return desktopFile.error() == QFile::NoError && desktopFile.flush();
|
||||
|
||||
} else {
|
||||
return !desktopFile.exists() || desktopFile.remove();
|
||||
}
|
||||
|
||||
#elif defined(PLATFORM_WINDOWS)
|
||||
auto settings = QSettings(QStringLiteral("HKEY_CURRENT_USER\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run"), QSettings::NativeFormat);
|
||||
if (enabled) {
|
||||
settings.setValue(QStringLiteral(PROJECT_NAME), QCoreApplication::applicationFilePath().replace(QChar('/'), QChar('\\')));
|
||||
} else {
|
||||
settings.remove(QStringLiteral(PROJECT_NAME));
|
||||
}
|
||||
settings.sync();
|
||||
return true;
|
||||
|
||||
#elif defined(PLATFORM_MAC)
|
||||
const auto libraryPath = QDir::home().filePath(QStringLiteral("Library"));
|
||||
if (enabled && !QDir().mkpath(libraryPath + QStringLiteral("/LaunchAgents"))) {
|
||||
return false;
|
||||
}
|
||||
auto launchdPlistFile = QFile(libraryPath + QStringLiteral("/LaunchAgents/" PROJECT_NAME ".plist"));
|
||||
if (enabled) {
|
||||
if (!launchdPlistFile.open(QFile::WriteOnly | QFile::Truncate)) {
|
||||
return false;
|
||||
}
|
||||
launchdPlistFile.write("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
|
||||
"<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n"
|
||||
"<plist version=\"1.0\">\n"
|
||||
" <dict>\n"
|
||||
" <key>Label</key>\n"
|
||||
" <string>" PROJECT_NAME "</string>\n"
|
||||
" <key>ProgramArguments</key>\n"
|
||||
" <array>\n"
|
||||
" <string>");
|
||||
launchdPlistFile.write(QCoreApplication::applicationFilePath().toUtf8().data());
|
||||
launchdPlistFile.write("</string>\n"
|
||||
" </array>\n"
|
||||
" <key>KeepAlive</key>\n"
|
||||
" <true/>\n"
|
||||
" </dict>\n"
|
||||
"</plist>\n");
|
||||
return launchdPlistFile.error() == QFile::NoError && launchdPlistFile.flush();
|
||||
|
||||
} else {
|
||||
return !launchdPlistFile.exists() || launchdPlistFile.remove();
|
||||
}
|
||||
#endif
|
||||
return setAutostartPath(enabled ? supposedPath : QString());
|
||||
}
|
||||
|
||||
bool AutostartOptionPage::apply()
|
||||
{
|
||||
if (m_unsupported) {
|
||||
return true; // don't treat this as an error
|
||||
}
|
||||
if (!setAutostartEnabled(ui()->autostartCheckBox->isChecked())) {
|
||||
errors() << QCoreApplication::translate("QtGui::AutostartOptionPage", "unable to modify startup entry");
|
||||
return false;
|
||||
|
@ -885,8 +957,31 @@ bool AutostartOptionPage::apply()
|
|||
|
||||
void AutostartOptionPage::reset()
|
||||
{
|
||||
if (hasBeenShown()) {
|
||||
if (!hasBeenShown() || m_unsupported) {
|
||||
return;
|
||||
}
|
||||
const auto configuredPath = configuredAutostartPath();
|
||||
if (!configuredPath.has_value()) { // we can't determine the currently configured path
|
||||
ui()->pathWidget->setVisible(false);
|
||||
ui()->autostartCheckBox->setEnabled(true);
|
||||
ui()->autostartCheckBox->setChecked(isAutostartEnabled());
|
||||
return;
|
||||
}
|
||||
const auto autostartEnabled = !configuredPath.value().isEmpty();
|
||||
ui()->autostartCheckBox->setChecked(autostartEnabled);
|
||||
if (!autostartEnabled) {
|
||||
ui()->pathWidget->setVisible(false);
|
||||
ui()->autostartCheckBox->setEnabled(true);
|
||||
return;
|
||||
}
|
||||
const auto supposedPath = supposedAutostartPath();
|
||||
const auto pathMismatch = configuredPath != supposedPath;
|
||||
ui()->pathWidget->setVisible(pathMismatch);
|
||||
ui()->autostartCheckBox->setEnabled(!pathMismatch);
|
||||
if (pathMismatch) {
|
||||
ui()->pathWarningLabel->setText(QCoreApplication::translate("QtGui::AutostartOptionPage", "There is already an autostart entry for \"%1\". "
|
||||
"It will not be overridden when applying changes unless you delete it first.")
|
||||
.arg(configuredPath.value()));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -15,6 +15,8 @@
|
|||
#include <QStringList>
|
||||
#include <QWidget>
|
||||
|
||||
#include <optional>
|
||||
|
||||
QT_FORWARD_DECLARE_CLASS(QAction)
|
||||
QT_FORWARD_DECLARE_CLASS(QLabel)
|
||||
|
||||
|
@ -101,7 +103,14 @@ struct {
|
|||
} m_widgets[Data::StatusIconSettings::distinguishableColorCount];
|
||||
END_DECLARE_OPTION_PAGE
|
||||
|
||||
DECLARE_UI_FILE_BASED_OPTION_PAGE_CUSTOM_SETUP(AutostartOptionPage)
|
||||
BEGIN_DECLARE_UI_FILE_BASED_OPTION_PAGE(AutostartOptionPage)
|
||||
private:
|
||||
bool m_unsupported = false;
|
||||
DECLARE_SETUP_WIDGETS
|
||||
END_DECLARE_OPTION_PAGE
|
||||
SYNCTHINGWIDGETS_EXPORT std::optional<QString> configuredAutostartPath();
|
||||
SYNCTHINGWIDGETS_EXPORT QString supposedAutostartPath();
|
||||
SYNCTHINGWIDGETS_EXPORT bool setAutostartPath(const QString &path);
|
||||
SYNCTHINGWIDGETS_EXPORT bool isAutostartEnabled();
|
||||
SYNCTHINGWIDGETS_EXPORT bool setAutostartEnabled(bool enabled);
|
||||
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
|
||||
<defs id="defs3051">
|
||||
<style type="text/css" id="current-color-scheme">
|
||||
.ColorScheme-NegativeText {
|
||||
color:#da4453;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<path
|
||||
style="fill:currentColor;fill-opacity:1;stroke:none"
|
||||
class="ColorScheme-NegativeText"
|
||||
d="m5 2v2h1v-1h4v1h1v-2h-5zm-3 3v1h2v8h8v-8h2v-1zm3 1h6v7h-6z"
|
||||
/>
|
||||
</svg>
|
After Width: | Height: | Size: 417 B |
|
@ -17,5 +17,6 @@
|
|||
<file>icons/hicolor/scalable/actions/appointment-new.svg</file>
|
||||
<file>icons/hicolor/scalable/actions/download.svg</file>
|
||||
<file>icons/hicolor/scalable/actions/window-pin.svg</file>
|
||||
<file>icons/hicolor/scalable/actions/edit-delete.svg</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
|
|
Loading…
Reference in New Issue