Skip to content

Commit

Permalink
Add setDefaultProtocolHandler() to qx-system
Browse files Browse the repository at this point in the history
Allows for registering an application to be the default handler for a
given scheme-based protocol.
  • Loading branch information
oblivioncth committed Oct 23, 2023
1 parent 13553a0 commit 3929b0d
Show file tree
Hide file tree
Showing 7 changed files with 158 additions and 0 deletions.
3 changes: 3 additions & 0 deletions lib/core/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
qx_add_component("Core"
HEADERS_PRIVATE
qx-json_p.h
qx-system_p.h
HEADERS_API
qx-abstracterror.h
qx-algorithm.h
Expand Down Expand Up @@ -57,6 +58,8 @@ qx_add_component("Core"
qx-system.cpp
qx-system_linux.cpp
qx-system_win.cpp
qx-system_p_win.cpp
qx-system_p_linux.cpp
qx-systemerror.cpp
qx-systemerror_linux.cpp
qx-systemerror_win.cpp
Expand Down
2 changes: 2 additions & 0 deletions lib/core/include/qx/core/qx-system.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ QX_CORE_EXPORT SystemError cleanKillProcess(quint32 processId);
QX_CORE_EXPORT SystemError forceKillProcess(quint32 processId);

QX_CORE_EXPORT bool enforceSingleInstance(QString uniqueAppId);

QX_CORE_EXPORT bool setDefaultProtocolHandler(const QString& scheme, const QString& name, const QString& path = {}, const QStringList& args = {});
}

#endif // QX_SYSTEM_H
42 changes: 42 additions & 0 deletions lib/core/src/qx-system.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
// Unit Includes
#include "qx/core/qx-system.h"
#include "qx-system_p.h"

// Qt Includes
#include <QDir>
#include <QCoreApplication>

/*!
* @file qx-system.h
Expand Down Expand Up @@ -123,4 +128,41 @@ bool processIsRunning(quint32 processId) { return processName(processId).isNull(
* changed in future revisions once set.
*/

/*!
* Sets the application at @a path as the default handler for URI requests of @a scheme for the
* current user. The registration is configued so that when a URL that uses the protocol is followed,
* the program at the given path will be executed with the scheme URL as the last argument. Generally, the user is shown a prompt with the friendly name of the application @a name
* when the protocol is used.
*
* @a scheme cannot contain whitespace. If @a path is left empty, it defaults to
* QDir::toNativeSeparators(QCoreApplication::applicationFilePath()).
*
* Commonly, applications are designed to handle scheme URLs as a singular argument:
*
* @code
* myapp myscheme://some-data-here
* @endcode
*
* as most operating system facilities that allow a user to select a default protocol handler do not
* for adding additional arguments; however, additional arguments can be provided via @a args, which
* are placed before the scheme URL.
*
* @note On Linux this function only works for distibutions that can utilize FreeDesktop
* XDG Desktop Entries.
*
* @warning The provided arugments are automatically quoted, but not escaped. If the provided arguments
* contain reserved characters, they will need to be escaped manually.
*/
bool setDefaultProtocolHandler(const QString& scheme, const QString& name, const QString& path, const QStringList& args)
{
if(scheme.contains(QChar::Space))
return false;

QString command = '"' + (!path.isEmpty() ? path : QDir::toNativeSeparators(QCoreApplication::applicationFilePath())) + '"';
if(!args.isEmpty())
command += uR"( ")"_s + args.join(uR"(", ")"_s) + '"';

return registerUriScheme(scheme, name, command);
}

}
3 changes: 3 additions & 0 deletions lib/core/src/qx-system_linux.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@
#include <QFile>
#include <QDir>
#include <QDirIterator>
#include <QSettings>
#include <QProcess>
#include <QStandardPaths>

// Inner-component Includes
#include <qx/core/qx-regularexpression.h>
Expand Down
17 changes: 17 additions & 0 deletions lib/core/src/qx-system_p.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#ifndef QX_SYSTEM_P_H
#define QX_SYSTEM_P_H

// Qt Includes
#include <QString>

namespace Qx
{
/*! @cond */

//-Component Private Functions--------------------------------------------------------------------
bool registerUriScheme(const QString& scheme, const QString& name, const QString& command);

/*! @endcond */
}

#endif // QX_SYSTEM_P_H
50 changes: 50 additions & 0 deletions lib/core/src/qx-system_p_linux.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// Unit Includes
#include "qx-system_p.h"

// Qt Includes

using namespace Qt::Literals::StringLiterals;

namespace Qx
{
/*! @cond */

bool registerUriScheme(const QString& scheme, const QString& name, const QString& command)
{
// Get desktop entry path
QString XDG_DATA_HOME = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation);
QString dEntryFilename = scheme + u"-scheme-handler.desktop"_s;
QString dEntryPath = XDG_DATA_HOME + u"/applications/"_s + dEntryFilename;
QString xSchemeHandler = u"x-scheme-handler/"_s + scheme;

// Create desktop entry
QSettings de(dEntryPath, QSettings::IniFormat);
de.beginGroup(u"Desktop Entry"_s);
de.setValue(u"Type"_s, u"Application"_s);
de.setValue(u"Name"_s, name);
de.setValue(u"Exec"_s, command + u" %u"_s); // %u is already passed as single param, no need for quotes
de.setValue(u"StartupNotify"_s, u"false"_s);
de.setValue(u"MimeType"_s, xSchemeHandler);
de.endGroup();

de.sync();
if(de.status() != QSettings::NoError)
return false;

// Register MIME type
QProcess xdgMime;
xdgMime.setProgram(u"xdg-mime"_s);
xdgMime.setArguments({u"default"_s, dEntryFilename, xSchemeHandler});
xdgMime.setStandardOutputFile(QProcess::nullDevice());
xdgMime.setStandardErrorFile(QProcess::nullDevice());
xdgMime.start();

return xdgMime.waitForFinished(3000) && xdgMime.exitStatus() == xdgMime.NormalExit && xdgMime.exitCode() == 0;

// Alternatively "xdg-settings set default-url-scheme-handler *scheme* *.desktop_file*" can be used
}

/*! @endcond */
}


41 changes: 41 additions & 0 deletions lib/core/src/qx-system_p_win.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// Unit Includes
#include "qx-system_p.h"

// Qt Includes
#include <QSettings>

using namespace Qt::Literals::StringLiterals;

namespace Qx
{
/*! @cond */

bool registerUriScheme(const QString& scheme, const QString& name, const QString& command)
{
/* Set registry keys
*
* The example registry key root used in the MS documentation is HKEY_CLASSES_ROOT, which is
* a merged view of system-wide and user-specific settings, that defaults to updating the
* system-wide settings when written to, and therefore requires admin priviledges. So instead
* we use HKEY_CURRENT_USER\SOFTWARE\Classes which is just the user-specific section.
*/
QSettings schemeKey(u"HKEY_CURRENT_USER\\SOFTWARE\\Classes"_s + scheme, QSettings::NativeFormat);
schemeKey.setValue(u"."_s, name);
schemeKey.setValue("URL Protocol", "");
schemeKey.setValue(u"shell/open/command/."_s, command + uR"( "%1")"_s);

// Save and return status
schemeKey.sync();
return schemeKey.status() == QSettings::NoError;

/* NOTE: The Microsoft specification recommends adding a DefaultIcon key to these entries
* with an executable based icon path, though I'm not sure if/how that's actually
* used. If that is ever added, we should check if adding an icon to the desktop entry
* of the Linux equivalent has an appreciable effect as well.
*/
}

/*! @endcond */
}


0 comments on commit 3929b0d

Please sign in to comment.