Skip to content

Commit

Permalink
Add default terminal (#265)
Browse files Browse the repository at this point in the history
* XdgDesktopFile: Add a public tryExec method

It should be public.
The functionality already existed in the private checkTryExec() function.
Code updated to use QStandardPaths::findExecutable(). It does a better job.

* Adds a default terminal implementation

xdg-utils doesn't provide a utility script to open the registered terminal
emulator. xdg-terminal exists since 2006 but it didn't make to the first
league. A Desktop environment should give it's user the power of choice. So
we implemented it.

The default terminal emulator is stored in qtxdg.conf files, which can be
stored in several locations. QSettings own search order is used.
Additionally, it is possible to define desktop environment-specific default
terminal in a file named desktop-qtxdg.conf where desktop is the name of
the desktop environment (taken from XDG_CURRENT_DESKTOP environment
variable). For example, ~/.config/lxqt-qtxdg.conf defines user specific
default terminal override for LXQt.

These desktop-specific overrides take precedence over the corresponding
non-desktop-specific file. If the desktop-specific file isn't found the
non-specific will *not* be used.
  • Loading branch information
luis-pereira authored Oct 25, 2021
1 parent 2fa794d commit c3dfba9
Show file tree
Hide file tree
Showing 11 changed files with 369 additions and 28 deletions.
7 changes: 7 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@ set(QTXDGX_INTREE_INCLUDEDIR "${CMAKE_CURRENT_BINARY_DIR}/InTreeBuild/include")

message(STATUS "Building ${PROJECT_NAME} with Qt ${Qt5Core_VERSION}")

file(GLOB QTXDG_CONFIG_FILES config/*.conf)

add_subdirectory(src)

if(BUILD_TESTS)
Expand Down Expand Up @@ -156,6 +158,11 @@ install(EXPORT
COMPONENT Devel
)

install(FILES ${QTXDG_CONFIG_FILES}
DESTINATION "${CMAKE_INSTALL_FULL_DATADIR}/lxqt"
COMPONENT Runtime
)

# uninstall target
configure_file(
"${CMAKE_CURRENT_SOURCE_DIR}/cmake/cmake_uninstall.cmake.in"
Expand Down
1 change: 1 addition & 0 deletions config/lxqt-qtxdg.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
TerminalEmulator=qterminal.desktop
1 change: 1 addition & 0 deletions config/qtxdg.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
TerminalEmulator=xterm.desktop
70 changes: 70 additions & 0 deletions src/qtxdg/xdgdefaultapps.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,38 @@
#include "xdgdefaultapps.h"

#include "xdgdesktopfile.h"
#include "xdgdirs.h"
#include "xdgmimeapps.h"

#include <QSet>
#include <QSettings>
#include <QString>
#include <QStringList>

#include <memory>
#include <vector>


static XdgDesktopFile *getTerminal(const QString &terminalName)
{
XdgDesktopFile *t = new XdgDesktopFile{};
if (t->load(terminalName) && t->isValid()) {
const QStringList cats = t->value(QL1S("Categories"), QString()).toString().split(QL1C(';'), Qt::SkipEmptyParts);
if (cats.contains(QL1S("TerminalEmulator"))) {
if (t->contains(QL1S("TryExec"))) {
if (t->tryExec()) {
return t;
}
} else {
return t;
}
}
}

delete t;
return nullptr;
}

static QStringList getWebBrowserProtocolsGet()
{
// Protocols needed to quailify a application as the default browser
Expand All @@ -52,6 +75,18 @@ static QStringList getWebBrowserProtocolsSet()
return webBrowserProtocolsSet;
}

static QString qtxdgConfigFilename()
{
// first find the DE's qtxdg.conf file
QByteArray qtxdgConfig("qtxdg");
QList<QByteArray> desktopsList = qgetenv("XDG_CURRENT_DESKTOP").toLower().split(':');
if (!desktopsList.isEmpty()) {
qtxdgConfig = desktopsList.at(0) + '-' + qtxdgConfig;
}

return QString::fromLocal8Bit(qtxdgConfig);
}

// returns the list of apps that are from category and support protocols
static QList<XdgDesktopFile *> categoryAndMimeTypeApps(const QString &category, const QStringList &protocols)
{
Expand Down Expand Up @@ -120,6 +155,17 @@ bool XdgDefaultApps::setFileManager(const XdgDesktopFile &app)
return setDefaultApp(QL1S("inode/directory"), app);
}

bool XdgDefaultApps::setTerminal(const XdgDesktopFile &app)
{
if (!app.isValid())
return false;

const QString configFile = qtxdgConfigFilename();
QSettings settings(QSettings::UserScope, configFile);
settings.setValue(QL1S("TerminalEmulator"), XdgDesktopFile::id(app.fileName()));
return true;
}

bool XdgDefaultApps::setWebBrowser(const XdgDesktopFile &app)
{
const QStringList protocols =
Expand All @@ -132,6 +178,30 @@ bool XdgDefaultApps::setWebBrowser(const XdgDesktopFile &app)
return true;
}

XdgDesktopFile *XdgDefaultApps::terminal()
{
const QString configFile = qtxdgConfigFilename();
QSettings settings(QSettings::UserScope, configFile);
const QString terminalName = settings.value(QL1S("TerminalEmulator"), QString()).toString();
return getTerminal(terminalName);
}

QList<XdgDesktopFile *> XdgDefaultApps::terminals()
{
XdgMimeApps db;
QList<XdgDesktopFile *> terminalList = db.categoryApps(QL1S("TerminalEmulator"));
QList<XdgDesktopFile *>::iterator it = terminalList.begin();
while (it != terminalList.end()) {
if ((*it)->isShown()) {
++it;
} else {
delete *it;
it = terminalList.erase(it);
}
}
return terminalList;
}

// To be qualified as the default browser all protocols must be set to the same
// valid application
XdgDesktopFile *XdgDefaultApps::webBrowser()
Expand Down
19 changes: 19 additions & 0 deletions src/qtxdg/xdgdefaultapps.h
Original file line number Diff line number Diff line change
Expand Up @@ -69,13 +69,32 @@ class QTXDG_API XdgDefaultApps {
*/
static bool setFileManager(const XdgDesktopFile &app);

/*!
* \brief Sets the default terminal emulator
* \param The app to be set as the default terminal emulator
* \return True if successful, false otherwise
*/
static bool setTerminal(const XdgDesktopFile &app);

/*!
* \brief Sets the default web browser
* \param The app to be set as the default web browser
* \return True if successful, false otherwise
*/
static bool setWebBrowser(const XdgDesktopFile &app);

/*!
* \brief Gets the default terminal emulator
* \return The default terminal emulator. nullptr if it's not set or an error ocurred.
*/
static XdgDesktopFile *terminal();

/*!
* \brief Gets the installed terminal emulators
* \return A list of installed terminal emulators
*/
static QList<XdgDesktopFile *> terminals();

/*!
* \brief Gets the default web browser
* \return The default web browser. nullptr if it's not set or a error ocurred.
Expand Down
64 changes: 36 additions & 28 deletions src/qtxdg/xdgdesktopfile.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
#include "xdgicon.h"
#include "application_interface.h" // generated interface for DBus org.freedesktop.Application
#include "xdgmimeapps.h"
#include "xdgdefaultapps.h"

#include <cstdlib>
#include <unistd.h>
Expand Down Expand Up @@ -87,7 +88,6 @@ static const QLatin1String urlKey("URL");
static const QLatin1String iconKey("Icon");

// Helper functions prototypes
bool checkTryExec(const QString& progName);
QString &doEscape(QString& str, const QHash<QChar,QChar> &repl);
QString &doUnEscape(QString& str, const QHash<QChar,QChar> &repl);
QString &escape(QString& str);
Expand Down Expand Up @@ -459,21 +459,37 @@ bool XdgDesktopFileData::startApplicationDetached(const XdgDesktopFile *q, const
if (startByDBus(action, urls))
return true;
}
QStringList args = action.isEmpty()
QStringList args;
QStringList appArgs = action.isEmpty()
? q->expandExecString(urls)
: XdgDesktopAction{*q, action}.expandExecString(urls);

if (args.isEmpty())
if (appArgs.isEmpty())
return false;

if (q->value(QLatin1String("Terminal")).toBool())
{
QString term = QString::fromLocal8Bit(qgetenv("TERM"));
if (term.isEmpty())
term = QLatin1String("xterm");
XdgDesktopFile *terminal = XdgDefaultApps::terminal();
QString terminalCommand;
if (terminal != nullptr && terminal->isValid())
{
terminalCommand = terminal->value(execKey).toString();
}
else
{
qWarning() << "XdgDesktopFileData::startApplicationDetached(): Using fallback terminal (xterm).";
terminalCommand = QStringLiteral("xterm");
}

delete terminal;

args.prepend(QLatin1String("-e"));
args.prepend(term);
args.append(QProcess::splitCommand(terminalCommand));
args.append(QLatin1String("-e"));
args.append(appArgs);
}
else
{
args = appArgs;
}

bool detach = StartDetachTruly::instance();
Expand Down Expand Up @@ -1203,23 +1219,6 @@ QStringList XdgDesktopFile::expandExecString(const QStringList& urls) const
}


bool checkTryExec(const QString& progName)
{
if (progName.startsWith(QDir::separator()))
return QFileInfo(progName).isExecutable();

const QStringList dirs = QFile::decodeName(qgetenv("PATH")).split(QLatin1Char(':'));

for (const QString &dir : dirs)
{
if (QFileInfo(QDir(dir), progName).isExecutable())
return true;
}

return false;
}


QString XdgDesktopFile::id(const QString &fileName, bool checkFileExists)
{
const QFileInfo f(fileName);
Expand Down Expand Up @@ -1330,14 +1329,23 @@ bool XdgDesktopFile::isSuitable(bool excludeHidden, const QString &environment)
}

// actually installed. If not, entry may not show in menus, etc.
QString s = value(QLatin1String("TryExec")).toString();
if (!s.isEmpty() && ! checkTryExec(s))
return false;
if (contains(QLatin1String("TryExec")))
return tryExec();

return true;
}


bool XdgDesktopFile::tryExec() const
{
const QString progName = value(QLatin1String("TryExec")).toString();
if (progName.isEmpty())
return false;

return (QStandardPaths::findExecutable(progName).isEmpty()) ? false : true;
}


QString expandDynamicUrl(QString url)
{
const QStringList env = QProcess::systemEnvironment();
Expand Down
9 changes: 9 additions & 0 deletions src/qtxdg/xdgdesktopfile.h
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,15 @@ class QTXDG_API XdgDesktopFile
*/
bool isSuitable(bool excludeHidden = true, const QString &environment = QString()) const;

/*! Check if the executable file on disk used to determine if the program
is actually installed. If the path is not an absolute path, the file
is looked up in the $PATH environment variable.
Check TryExec entry existence with contains().
@return false if the file is not present or if it is not executable.
If the TryExec entry isn't present returns false
*/
bool tryExec() const;

protected:
virtual QString prefix() const { return QLatin1String("Desktop Entry"); }
virtual bool check() const { return true; }
Expand Down
1 change: 1 addition & 0 deletions src/tools/mat/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ add_executable(qtxdg-mat
defwebbrowsermatcommand.cpp
defemailclientmatcommand.cpp
deffilemanagermatcommand.cpp
defterminalmatcommand.cpp

qtxdg-mat.cpp
)
Expand Down
Loading

0 comments on commit c3dfba9

Please sign in to comment.