Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,14 @@ find_package(Qt${QT_VERSION_MAJOR}Core5Compat ${REQUIRED_QT_VERSION} CONFIG QUIE
TYPE REQUIRED
)

if(UNIX AND NOT APPLE)
find_package(Qt${QT_VERSION_MAJOR}DBus ${REQUIRED_QT_VERSION} CONFIG QUIET)
set_package_properties(Qt${QT_VERSION_MAJOR}DBus PROPERTIES
DESCRIPTION "Qt${QT_VERSION_MAJOR} DBus component."
TYPE REQUIRED
)
endif()

get_target_property (QT_QMAKE_EXECUTABLE Qt::qmake IMPORTED_LOCATION)
message(STATUS "Using Qt ${Qt${QT_MAJOR_VERSION}Core_VERSION} (${QT_QMAKE_EXECUTABLE})")

Expand Down
2 changes: 2 additions & 0 deletions src/common/common.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ elseif(APPLE)
elseif(UNIX AND NOT APPLE)
list(APPEND common_SOURCES
${CMAKE_CURRENT_LIST_DIR}/utility_unix.cpp
${CMAKE_CURRENT_LIST_DIR}/xdg_portal.h
${CMAKE_CURRENT_LIST_DIR}/xdg_portal.cpp
)
endif()

Expand Down
4 changes: 4 additions & 0 deletions src/common/utility.h
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,10 @@ namespace Utility {
OCSYNC_EXPORT uint convertSizeToUint(size_t &convertVar);
OCSYNC_EXPORT int convertSizeToInt(size_t &convertVar);

#ifdef Q_OS_UNIX
OCSYNC_EXPORT QString getAppExecutablePath();
#endif

#ifdef Q_OS_WIN
OCSYNC_EXPORT DWORD convertSizeToDWORD(size_t &convertVar);
#endif
Expand Down
84 changes: 52 additions & 32 deletions src/common/utility_unix.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

#include "utility.h"
#include "config.h"
#include "xdg_portal.h"

#include <QCoreApplication>
#include <QDir>
Expand Down Expand Up @@ -72,6 +73,10 @@ bool Utility::hasSystemLaunchOnStartup(const QString &appName)

bool Utility::hasLaunchOnStartup(const QString &appName)
{
//! if we are using portals this check will not work, that's why its usage in generalsettings.cpp is currently disabled.
//! as far as i can tell the portal does not let us do this check. how badly do we want it? the only two times its used is
//! that time in generalsettings.cpp and in tests, for settings as far as i can tell that check isn't really *needed* and
//! we might want to adapt tests to portals anyway...
const QString desktopFileLocation = getUserAutostartDir() + appName + QLatin1String(".desktop");
return QFile::exists(desktopFileLocation);
}
Expand All @@ -96,43 +101,58 @@ QString Utility::syncFolderDisplayName(const QString &folder, const QString &dis

void Utility::setLaunchOnStartup(const QString &appName, const QString &guiName, bool enable)
{
const auto userAutoStartPath = getUserAutostartDir();
const QString desktopFileLocation = userAutoStartPath + appName + QLatin1String(".desktop");
if (enable) {
if (!QDir().exists(userAutoStartPath) && !QDir().mkpath(userAutoStartPath)) {
qCWarning(lcUtility) << "Could not create autostart folder" << userAutoStartPath;
return;
}
QFile iniFile(desktopFileLocation);
if (!iniFile.open(QIODevice::WriteOnly)) {
qCWarning(lcUtility) << "Could not write auto start entry" << desktopFileLocation;
return;
}
// When running inside an AppImage, we need to set the path to the
// AppImage instead of the path to the executable
const QString appImagePath = qEnvironmentVariable("APPIMAGE");
const bool runningInsideAppImage = !appImagePath.isNull() && QFile::exists(appImagePath);
const QString executablePath = runningInsideAppImage ? appImagePath : QCoreApplication::applicationFilePath();

QTextStream ts(&iniFile);
ts << QLatin1String("[Desktop Entry]\n")
<< QLatin1String("Name=") << guiName << QLatin1Char('\n')
<< QLatin1String("GenericName=") << QLatin1String("File Synchronizer\n")
<< QLatin1String("Exec=\"") << executablePath << "\" --background\n"
<< QLatin1String("Terminal=") << "false\n"
<< QLatin1String("Icon=") << APPLICATION_ICON_NAME << QLatin1Char('\n')
<< QLatin1String("Categories=") << QLatin1String("Network\n")
<< QLatin1String("Type=") << QLatin1String("Application\n")
<< QLatin1String("StartupNotify=") << "false\n"
<< QLatin1String("X-GNOME-Autostart-enabled=") << "true\n"
<< QLatin1String("X-GNOME-Autostart-Delay=10") << Qt::endl;
const bool usePortals = true; // TODO: how should this be configured? probably a build flag? or should we just default always use portals and if the call fails *then* fallback? XdgPortal::background does return a bool for if it was successful so that wouldn't be a difficulty.
if (usePortals) {
XdgPortal portal;
portal.background(appName, enable);
} else {
if (!QFile::remove(desktopFileLocation)) {
qCWarning(lcUtility) << "Could not remove autostart desktop file";
const auto userAutoStartPath = getUserAutostartDir();
const QString desktopFileLocation = userAutoStartPath + appName + QLatin1String(".desktop");
if (enable) {
if (!QDir().exists(userAutoStartPath) && !QDir().mkpath(userAutoStartPath)) {
qCWarning(lcUtility) << "Could not create autostart folder" << userAutoStartPath;
return;
}
QFile iniFile(desktopFileLocation);
if (!iniFile.open(QIODevice::WriteOnly)) {
qCWarning(lcUtility) << "Could not write auto start entry" << desktopFileLocation;
return;
}

const auto executablePath = Utility::getAppExecutablePath();

QTextStream ts(&iniFile);
ts << QLatin1String("[Desktop Entry]\n")
<< QLatin1String("Name=") << guiName << QLatin1Char('\n')
<< QLatin1String("GenericName=") << QLatin1String("File Synchronizer\n")
<< QLatin1String("Exec=\"") << executablePath << "\" --background\n"
<< QLatin1String("Terminal=") << "false\n"
<< QLatin1String("Icon=") << APPLICATION_ICON_NAME << QLatin1Char('\n')
<< QLatin1String("Categories=") << QLatin1String("Network\n")
<< QLatin1String("Type=") << QLatin1String("Application\n")
<< QLatin1String("StartupNotify=") << "false\n"
<< QLatin1String("X-GNOME-Autostart-enabled=") << "true\n"
<< QLatin1String("X-GNOME-Autostart-Delay=10") << Qt::endl;
} else {
if (!QFile::remove(desktopFileLocation)) {
qCWarning(lcUtility) << "Could not remove autostart desktop file";
}
}
}
}

QString Utility::getAppExecutablePath()
{
// When running inside an AppImage, we need to set the path to the
// AppImage instead of the path to the executable
const QString appImagePath = qEnvironmentVariable("APPIMAGE");
const QString flatpakId = qEnvironmentVariable("FLATPAK_ID");
const bool runningInsideAppImage = !appImagePath.isNull() && QFile::exists(appImagePath);
const bool runningInsideFlatpak = !flatpakId.isNull();
const QString executablePath = runningInsideAppImage ? QLatin1String("\"%1\"").arg(appImagePath) : runningInsideFlatpak ? QLatin1String("flatpak run %1").arg(flatpakId) : QLatin1String("\"%1\"").arg(QCoreApplication::applicationFilePath());
return executablePath;
}

bool Utility::hasDarkSystray()
{
return true;
Expand Down
84 changes: 84 additions & 0 deletions src/common/xdg_portal.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
/*
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: GPL-2.0-or-later
*/

#include "xdg_portal.h"
#include "utility.h"
#include <QtDBus/QDBusConnection>
#include <QtDBus/QDBusConnectionInterface>
#include <QtDBus/QDBusInterface>
#include <QtDBus/QDBusMessage>
#include <QDebug>
#include <QFileInfo>
#include <QCoreApplication>

namespace OCC {

constexpr auto portalDesktopService = "org.freedesktop.portal.Desktop";
constexpr auto portalDesktopPath = "/org/freedesktop/portal/desktop";

XdgPortal::XdgPortal(QObject *parent)
: QObject(parent)
, m_available(false)
{
initPortalInterface();
}

void XdgPortal::initPortalInterface()
{
auto *busInterface = QDBusConnection::sessionBus().interface();
if (!busInterface) {
qWarning() << "XDG Desktop Portal not available";
return;
}

const auto registered = busInterface->isServiceRegistered(QLatin1String(portalDesktopService));
m_available = registered.isValid() && registered.value();
if (!m_available) {
qWarning() << "XDG Desktop Portal not available";
}
}

bool XdgPortal::background(const QString &handle_token, const bool &autostart)
{
if (!m_available) {
return false;
}

QDBusInterface backgroundInterface(
QLatin1String(portalDesktopService),
QLatin1String(portalDesktopPath),
QLatin1String("org.freedesktop.portal.Background"),
QDBusConnection::sessionBus());
if (!backgroundInterface.isValid()) { // just in case
qWarning() << "org.freedesktop.portal.Background not available";
return false;
}

QVariantMap options;
options[QLatin1String("autostart")] = autostart;
const QString flatpakId = qEnvironmentVariable("FLATPAK_ID");
if (flatpakId.isNull()) {
QStringList list = {Utility::getAppExecutablePath(), QLatin1String("--background")};
options[QLatin1String("commandline")] = list;
} else { // when an app is running as a flatpak its "commandline" parameter is handled differently
QStringList list = {QFileInfo(QCoreApplication::applicationFilePath()).fileName(), QLatin1String("--background")};
options[QLatin1String("commandline")] = list;
}

QDBusMessage reply = backgroundInterface.call(
QLatin1String("RequestBackground"),
handle_token,
options
);

if (reply.type() == QDBusMessage::ErrorMessage) {
qWarning() << "Background request failed: " << reply.errorMessage();
return false;
}

return true;
}

} // namespace OCC
43 changes: 43 additions & 0 deletions src/common/xdg_portal.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: GPL-2.0-or-later
*/

#ifndef XDG_PORTAL_H
#define XDG_PORTAL_H

#include <QObject>
#include <QString>
#include <QVariantMap>

namespace OCC {

/**
* @brief XDG Desktop Portal interface for Qt D-Bus
*
* Adstracts D-Bus calls behind simpler methods.
*/
class XdgPortal : public QObject
{
Q_OBJECT

public:
explicit XdgPortal(QObject *parent = nullptr);

/**
* @brief Requests that the application is allowed to run in the background; see https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.Background.html
* @param handle_token A string that will be used as the last element of the handle. Must be a valid object path element. See the [Request](https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.Request.html#org-freedesktop-portal-request) documentation for more information about the handle.
* @param autostart true if the app also wants to be started automatically at login.
* @return True if successful
*/
bool background(const QString &handle_token, const bool &autostart);

private:
bool m_available;

void initPortalInterface();
};

} // namespace OCC

#endif // XDG_PORTAL_H
7 changes: 4 additions & 3 deletions src/gui/generalsettings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -651,9 +651,10 @@ void GeneralSettings::saveMiscSettings()
void GeneralSettings::slotToggleLaunchOnStartup(bool enable)
{
const auto theme = Theme::instance();
if (enable == Utility::hasLaunchOnStartup(theme->appName())) {
return;
}
//! please see Utility::hasLaunchOnStartup for why this is disabled
// if (enable == Utility::hasLaunchOnStartup(theme->appName())) {
// return;
// }

ConfigFile configFile;
configFile.setLaunchOnSystemStartup(enable);
Expand Down