diff --git a/CMakeLists.txt b/CMakeLists.txt index 2272f42..4e6d65a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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) @@ -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" diff --git a/config/lxqt-qtxdg.conf b/config/lxqt-qtxdg.conf new file mode 100644 index 0000000..170fcd0 --- /dev/null +++ b/config/lxqt-qtxdg.conf @@ -0,0 +1 @@ +TerminalEmulator=qterminal.desktop diff --git a/config/qtxdg.conf b/config/qtxdg.conf new file mode 100644 index 0000000..fc566bb --- /dev/null +++ b/config/qtxdg.conf @@ -0,0 +1 @@ +TerminalEmulator=xterm.desktop diff --git a/src/qtxdg/xdgdefaultapps.cpp b/src/qtxdg/xdgdefaultapps.cpp index 3160ca6..48322f0 100644 --- a/src/qtxdg/xdgdefaultapps.cpp +++ b/src/qtxdg/xdgdefaultapps.cpp @@ -21,15 +21,38 @@ #include "xdgdefaultapps.h" #include "xdgdesktopfile.h" +#include "xdgdirs.h" #include "xdgmimeapps.h" #include +#include #include #include #include #include + +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 @@ -52,6 +75,18 @@ static QStringList getWebBrowserProtocolsSet() return webBrowserProtocolsSet; } +static QString qtxdgConfigFilename() +{ + // first find the DE's qtxdg.conf file + QByteArray qtxdgConfig("qtxdg"); + QList 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 categoryAndMimeTypeApps(const QString &category, const QStringList &protocols) { @@ -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 = @@ -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 XdgDefaultApps::terminals() +{ + XdgMimeApps db; + QList terminalList = db.categoryApps(QL1S("TerminalEmulator")); + QList::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() diff --git a/src/qtxdg/xdgdefaultapps.h b/src/qtxdg/xdgdefaultapps.h index 9efd78a..3c567c5 100644 --- a/src/qtxdg/xdgdefaultapps.h +++ b/src/qtxdg/xdgdefaultapps.h @@ -69,6 +69,13 @@ 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 @@ -76,6 +83,18 @@ class QTXDG_API XdgDefaultApps { */ 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 terminals(); + /*! * \brief Gets the default web browser * \return The default web browser. nullptr if it's not set or a error ocurred. diff --git a/src/qtxdg/xdgdesktopfile.cpp b/src/qtxdg/xdgdesktopfile.cpp index 8311a2e..e1a0a68 100644 --- a/src/qtxdg/xdgdesktopfile.cpp +++ b/src/qtxdg/xdgdesktopfile.cpp @@ -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 #include @@ -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 &repl); QString &doUnEscape(QString& str, const QHash &repl); QString &escape(QString& str); @@ -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(); @@ -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); @@ -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(); diff --git a/src/qtxdg/xdgdesktopfile.h b/src/qtxdg/xdgdesktopfile.h index 7e04281..e368e3c 100644 --- a/src/qtxdg/xdgdesktopfile.h +++ b/src/qtxdg/xdgdesktopfile.h @@ -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; } diff --git a/src/tools/mat/CMakeLists.txt b/src/tools/mat/CMakeLists.txt index 78870ed..d3c1ce9 100644 --- a/src/tools/mat/CMakeLists.txt +++ b/src/tools/mat/CMakeLists.txt @@ -7,6 +7,7 @@ add_executable(qtxdg-mat defwebbrowsermatcommand.cpp defemailclientmatcommand.cpp deffilemanagermatcommand.cpp + defterminalmatcommand.cpp qtxdg-mat.cpp ) diff --git a/src/tools/mat/defterminalmatcommand.cpp b/src/tools/mat/defterminalmatcommand.cpp new file mode 100644 index 0000000..e4d5f71 --- /dev/null +++ b/src/tools/mat/defterminalmatcommand.cpp @@ -0,0 +1,187 @@ +/* + * libqtxdg - An Qt implementation of freedesktop.org xdg specs + * Copyright (C) 2021 Luís Pereira + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA + */ + +#include "defterminalmatcommand.h" + +#include "matglobals.h" +#include "xdgmacros.h" +#include "xdgdefaultapps.h" +#include "xdgdesktopfile.h" + +#include +#include +#include +#include +#include +#include +#include + +#include + +enum DefTerminalCommandMode { + CommandModeGetDefTerminal, + CommandModeSetDefTerminal, + CommandModeListAvailableTerminals, +}; + +struct DefTerminalData { + DefTerminalData() : mode(CommandModeGetDefTerminal) {} + + DefTerminalCommandMode mode; + QString defTerminalName; +}; + +static CommandLineParseResult parseCommandLine(QCommandLineParser *parser, DefTerminalData *data, QString *errorMessage) +{ + parser->clearPositionalArguments(); + parser->setApplicationDescription(QL1S("Get/Set the default terminal")); + + parser->addPositionalArgument(QL1S("def-terminal"), QL1S()); + + const QCommandLineOption defTerminalNameOption(QStringList() << QSL("s") << QSL("set"), + QSL("Terminal to be set as default"), QSL("terminal")); + + const QCommandLineOption listAvailableOption(QStringList() << QSL("l") << QSL("list-available"), + QSL("List available terminals")); + + parser->addOption(defTerminalNameOption); + parser->addOption(listAvailableOption); + const QCommandLineOption helpOption = parser->addHelpOption(); + const QCommandLineOption versionOption = parser->addVersionOption(); + + if (!parser->parse(QCoreApplication::arguments())) { + *errorMessage = parser->errorText(); + return CommandLineError; + } + + if (parser->isSet(versionOption)) { + return CommandLineVersionRequested; + } + + if (parser->isSet(helpOption)) { + return CommandLineHelpRequested; + } + + const bool isListAvailableSet = parser->isSet(listAvailableOption); + const bool isDefTerminalNameSet = parser->isSet(defTerminalNameOption); + QString defTerminalName; + + if (isDefTerminalNameSet) + defTerminalName = parser->value(defTerminalNameOption); + + QStringList posArgs = parser->positionalArguments(); + posArgs.removeAt(0); + + if (isDefTerminalNameSet && !posArgs.empty()) { + *errorMessage = QSL("Extra arguments given: "); + errorMessage->append(posArgs.join(QLatin1Char(','))); + return CommandLineError; + } + + if (!isDefTerminalNameSet && !posArgs.empty()) { + *errorMessage = QSL("To set the default terminal use the -s/--set option"); + return CommandLineError; + } + + if (isListAvailableSet && (isDefTerminalNameSet || !posArgs.empty())) { + *errorMessage = QSL("list-available can't be used with other options and doesn't take arguments"); + return CommandLineError; + } + + if (isListAvailableSet) { + data->mode = CommandModeListAvailableTerminals; + } else { + data->mode = isDefTerminalNameSet ? CommandModeSetDefTerminal: CommandModeGetDefTerminal; + data->defTerminalName = defTerminalName; + } + + return CommandLineOk; +} + +DefTerminalMatCommand::DefTerminalMatCommand(QCommandLineParser *parser) + : MatCommandInterface(QL1S("def-terminal"), + QSL("Get/Set the default terminal"), + parser) +{ + Q_CHECK_PTR(parser); +} + +DefTerminalMatCommand::~DefTerminalMatCommand() = default; + +int DefTerminalMatCommand::run(const QStringList & /*arguments*/) +{ + bool success = true; + DefTerminalData data; + QString errorMessage; + if (!MatCommandInterface::parser()) { + qFatal("DefTerminalMatCommand::run: MatCommandInterface::parser() returned a null pointer"); + } + switch(parseCommandLine(parser(), &data, &errorMessage)) { + case CommandLineOk: + break; + case CommandLineError: + std::cerr << qPrintable(errorMessage); + std::cerr << "\n\n"; + std::cerr << qPrintable(parser()->helpText()); + return EXIT_FAILURE; + case CommandLineVersionRequested: + showVersion(); + Q_UNREACHABLE(); + case CommandLineHelpRequested: + showHelp(); + Q_UNREACHABLE(); + } + + if (data.mode == CommandModeListAvailableTerminals) { + const auto terminals = XdgDefaultApps::terminals(); + for (const auto *terminal : terminals) { + QFileInfo fi{terminal->fileName()}; + std::cout << qPrintable(fi.fileName()) << "\n"; + } + + qDeleteAll(terminals); + return EXIT_SUCCESS; + } + + if (data.mode == CommandModeGetDefTerminal) { // Get default terminal + XdgDesktopFile *defTerminal = XdgDefaultApps::terminal(); + if (defTerminal != nullptr && defTerminal->isValid()) { + QFileInfo f(defTerminal->fileName()); + std::cout << qPrintable(f.fileName()) << "\n"; + delete defTerminal; + } + } else { // Set default terminal + XdgDesktopFile toSetDefTerminal; + if (toSetDefTerminal.load(data.defTerminalName)) { + if (XdgDefaultApps::setTerminal(toSetDefTerminal)) { + std::cout << qPrintable(QSL("Set '%1' as the default terminal\n").arg(toSetDefTerminal.fileName())); + } else { + std::cerr << qPrintable(QSL("Could not set '%1' as the default terminal\n").arg(toSetDefTerminal.fileName())); + success = false; + } + } else { // could not load application file + std::cerr << qPrintable(QSL("Could not find find '%1'\n").arg(data.defTerminalName)); + success = false; + } + } + + return success ? EXIT_SUCCESS : EXIT_FAILURE; +} + diff --git a/src/tools/mat/defterminalmatcommand.h b/src/tools/mat/defterminalmatcommand.h new file mode 100644 index 0000000..3baebb2 --- /dev/null +++ b/src/tools/mat/defterminalmatcommand.h @@ -0,0 +1,34 @@ +/* + * libqtxdg - An Qt implementation of freedesktop.org xdg specs + * Copyright (C) 2021 Luís Pereira + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA + */ + +#ifndef DEFTERMINALMATCOMMAND_H +#define DEFTERMINALMATCOMMAND_H + +#include "matcommandinterface.h" + +class DefTerminalMatCommand : public MatCommandInterface { +public: + explicit DefTerminalMatCommand(QCommandLineParser *parser); + ~DefTerminalMatCommand() override; + + int run(const QStringList &arguments) override; +}; + +#endif // DEFTERMINALMATCOMMAND_H diff --git a/src/tools/mat/qtxdg-mat.cpp b/src/tools/mat/qtxdg-mat.cpp index 61956ed..80cab1a 100644 --- a/src/tools/mat/qtxdg-mat.cpp +++ b/src/tools/mat/qtxdg-mat.cpp @@ -25,6 +25,7 @@ #include "defwebbrowsermatcommand.h" #include "defemailclientmatcommand.h" #include "deffilemanagermatcommand.h" +#include "defterminalmatcommand.h" #include "xdgmacros.h" @@ -88,6 +89,9 @@ int main(int argc, char *argv[]) MatCommandInterface *const defFileManagerCmd = new DefFileManagerMatCommand(&parser); manager->add(defFileManagerCmd); + MatCommandInterface *const defTerminalCmd = new DefTerminalMatCommand(&parser); + manager->add(defTerminalCmd); + // Find out the positional arguments. parser.parse(QCoreApplication::arguments()); const QStringList args = parser.positionalArguments();