diff --git a/syncthingwidgets/settings/autostartoptionpage.ui b/syncthingwidgets/settings/autostartoptionpage.ui
index 2fb2baf..2654f12 100644
--- a/syncthingwidgets/settings/autostartoptionpage.ui
+++ b/syncthingwidgets/settings/autostartoptionpage.ui
@@ -12,14 +12,14 @@
Autostart
-
+
+ ..
-
- 75
true
@@ -28,6 +28,61 @@
+ -
+
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
-
+
+
+
+ 32
+ 32
+
+
+
+
+ 32
+ 32
+
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ true
+
+
+
+ -
+
+
+ Delete existing entry
+
+
+
+ :/icons/hicolor/scalable/actions/edit-delete.svg:/icons/hicolor/scalable/actions/edit-delete.svg
+
+
+
+
+
+
-
-
@@ -63,6 +118,8 @@
-
+
+
+
diff --git a/syncthingwidgets/settings/settingsdialog.cpp b/syncthingwidgets/settings/settingsdialog.cpp
index e42e182..17a5056 100644
--- a/syncthingwidgets/settings/settingsdialog.cpp
+++ b/syncthingwidgets/settings/settingsdialog.cpp
@@ -57,6 +57,8 @@
#include
#elif defined(PLATFORM_WINDOWS)
#include
+#include
+#include
#elif defined(PLATFORM_MAC)
#include
#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 ~/.config/autostart 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 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("\n"
+ "\n"
+ "\n"
+ " \n"
+ " Label\n"
+ " " PROJECT_NAME "\n"
+ " ProgramArguments\n"
+ " \n"
+ " ");
+ launchdPlistFile.write(path.toUtf8());
+ launchdPlistFile.write("\n"
+ " \n"
+ " KeepAlive\n"
+ " \n"
+ " \n"
+ "\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("\n"
- "\n"
- "\n"
- " \n"
- " Label\n"
- " " PROJECT_NAME "\n"
- " ProgramArguments\n"
- " \n"
- " ");
- launchdPlistFile.write(QCoreApplication::applicationFilePath().toUtf8().data());
- launchdPlistFile.write("\n"
- " \n"
- " KeepAlive\n"
- " \n"
- " \n"
- "\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()));
}
}
diff --git a/syncthingwidgets/settings/settingsdialog.h b/syncthingwidgets/settings/settingsdialog.h
index 74fb5b7..7350e1d 100644
--- a/syncthingwidgets/settings/settingsdialog.h
+++ b/syncthingwidgets/settings/settingsdialog.h
@@ -15,6 +15,8 @@
#include
#include
+#include
+
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 configuredAutostartPath();
+SYNCTHINGWIDGETS_EXPORT QString supposedAutostartPath();
+SYNCTHINGWIDGETS_EXPORT bool setAutostartPath(const QString &path);
SYNCTHINGWIDGETS_EXPORT bool isAutostartEnabled();
SYNCTHINGWIDGETS_EXPORT bool setAutostartEnabled(bool enabled);
diff --git a/tray/resources/icons/hicolor/scalable/actions/edit-delete.svg b/tray/resources/icons/hicolor/scalable/actions/edit-delete.svg
new file mode 100644
index 0000000..9dfb2e0
--- /dev/null
+++ b/tray/resources/icons/hicolor/scalable/actions/edit-delete.svg
@@ -0,0 +1,14 @@
+
diff --git a/tray/resources/syncthingtrayicons.qrc b/tray/resources/syncthingtrayicons.qrc
index c7424ba..c15df7b 100644
--- a/tray/resources/syncthingtrayicons.qrc
+++ b/tray/resources/syncthingtrayicons.qrc
@@ -17,5 +17,6 @@
icons/hicolor/scalable/actions/appointment-new.svg
icons/hicolor/scalable/actions/download.svg
icons/hicolor/scalable/actions/window-pin.svg
+ icons/hicolor/scalable/actions/edit-delete.svg