diff --git a/.ci/build.sh b/.ci/build.sh
index bd44971c..d6ae7b0c 100644
--- a/.ci/build.sh
+++ b/.ci/build.sh
@@ -7,3 +7,4 @@ pacman -S --noconfirm --needed git cmake lxqt-build-tools-git qt5-tools qtermwid
cmake -B build -S .
make -C build
+ARGS="-V" make -C build test
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 1f6fd520..0c85875a 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -12,6 +12,7 @@ include(GNUInstallDirs)
set(QTERMINAL_VERSION "1.1.0")
option(UPDATE_TRANSLATIONS "Update source translation translations/*.ts files" OFF)
+option(BUILD_TESTS "Builds tests" ON)
if(APPLE)
option(APPLEBUNDLE "Build as qterminal.app bundle" ON)
@@ -36,6 +37,10 @@ endif()
find_package(QTermWidget5 ${QTERMWIDGET_MINIMUM_VERSION} REQUIRED)
find_package(lxqt-build-tools ${LXQTBT_MINIMUM_VERSION} REQUIRED)
+if (BUILD_TESTS)
+ find_package(Qt5 ${QT_MINIMUM_VERSION} CONFIG REQUIRED Test)
+endif()
+
include(LXQtPreventInSourceBuilds)
include(LXQtTranslateTs)
include(LXQtTranslateDesktop)
@@ -75,6 +80,7 @@ set(QTERM_SRC
src/fontdialog.cpp
src/dbusaddressable.cpp
src/tab-switcher.cpp
+ src/qterminalutils.cpp
)
set(QTERM_MOC_SRC
@@ -265,3 +271,8 @@ else()
COMMAND "${CMAKE_COMMAND}" -E touch "${CMAKE_CURRENT_BINARY_DIR}/${EXE_NAME}.app/Contents/Resources/empty.lproj"
COMMENT "Creating Resources/empty.lproj")
endif()
+
+if(BUILD_TESTS)
+ enable_testing()
+ add_subdirectory(test)
+endif()
diff --git a/src/main.cpp b/src/main.cpp
index 581be135..23ce9173 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -33,6 +33,7 @@
#include "mainwindow.h"
#include "qterminalapp.h"
+#include "qterminalutils.h"
#include "terminalconfig.h"
#define out
@@ -72,7 +73,7 @@ QTerminalApp * QTerminalApp::m_instance = nullptr;
exit(code);
}
-void parse_args(int argc, char* argv[], QString& workdir, QString & shell_command, out bool& dropMode)
+void parse_args(int argc, char* argv[], QString& workdir, QStringList & shell_command, out bool& dropMode)
{
int next_option;
dropMode = false;
@@ -87,13 +88,13 @@ void parse_args(int argc, char* argv[], QString& workdir, QString & shell_comman
workdir = QString::fromLocal8Bit(optarg);
break;
case 'e':
- shell_command = QString::fromLocal8Bit(optarg);
+ shell_command << parse_command(QString::fromLocal8Bit(optarg));
// #15 "Raw" -e params
// Passing "raw" params (like konsole -e mcedit /tmp/tmp.txt") is more preferable - then I can call QString("qterminal -e ") + cmd_line in other programs
while (optind < argc)
{
//printf("arg: %d - %s\n", optind, argv[optind]);
- shell_command += QLatin1Char(' ') + QString::fromLocal8Bit(argv[optind++]);
+ shell_command << QString::fromLocal8Bit(argv[optind++]);
}
break;
case 'd':
@@ -141,7 +142,8 @@ int main(int argc, char *argv[])
app->registerOnDbus();
#endif
- QString workdir, shell_command;
+ QString workdir;
+ QStringList shell_command;
bool dropMode;
parse_args(argc, argv, workdir, shell_command, dropMode);
diff --git a/src/properties.h b/src/properties.h
index 7e600335..ff97df32 100644
--- a/src/properties.h
+++ b/src/properties.h
@@ -50,7 +50,7 @@ class Properties
QPoint mainWindowPosition;
QByteArray mainWindowState;
//ShortcutMap shortcuts;
- QString shell;
+ QStringList shell;
QFont font;
QString colorScheme;
QString guiStyle;
diff --git a/src/qterminalutils.cpp b/src/qterminalutils.cpp
new file mode 100644
index 00000000..62ce95e2
--- /dev/null
+++ b/src/qterminalutils.cpp
@@ -0,0 +1,64 @@
+/***************************************************************************
+ * Copyright (C) 2022 by LXQt team *
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of the GNU General Public License as published by *
+ * the Free Software Foundation; either version 2 of the License, or *
+ * (at your option) any later version. *
+ * *
+ * This program 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 General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU General Public License *
+ * along with this program. If not, see . *
+ ***************************************************************************/
+
+#include
+
+#include "qterminalutils.h"
+
+QStringList parse_command(const QString& str)
+{
+ const QRegularExpression separator(QString::fromLatin1(R"('|(? index)
+ {
+ list << str.mid(index, nextIndex - index).replace(escapedSpace, QStringLiteral(" "));
+ }
+ if (match.capturedLength() == 0)
+ { // end of string ("\z") is matched
+ break;
+ }
+ index = nextIndex + match.capturedLength();
+ auto c = str.at(index - 1); // last matched character
+ if (!c.isSpace())
+ { // a single quote or an unescaped double quote is matched
+ nextIndex = str.indexOf(c == QLatin1Char('\'') ? singleQuote : doubleQuote, index, &match);
+ if (nextIndex == -1)
+ { // the quote is not closed
+ break;
+ }
+ else
+ {
+ if (nextIndex > index)
+ {
+ list << str.mid(index, nextIndex - index).replace(escapedSpace, QStringLiteral(" "));
+ }
+ index = nextIndex + match.capturedLength();
+ }
+ }
+ }
+ return list;
+}
+
diff --git a/src/qterminalutils.h b/src/qterminalutils.h
new file mode 100644
index 00000000..0b8d782c
--- /dev/null
+++ b/src/qterminalutils.h
@@ -0,0 +1,26 @@
+/***************************************************************************
+ * Copyright (C) 2022 by LXQt team *
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of the GNU General Public License as published by *
+ * the Free Software Foundation; either version 2 of the License, or *
+ * (at your option) any later version. *
+ * *
+ * This program 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 General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU General Public License *
+ * along with this program. If not, see . *
+ ***************************************************************************/
+
+#ifndef QTERMINALUTILS_H
+#define QTERMINALUTILS_H
+
+#include
+#include
+
+QStringList parse_command(const QString& str);
+
+#endif
diff --git a/src/terminalconfig.cpp b/src/terminalconfig.cpp
index f61e014c..1497a298 100644
--- a/src/terminalconfig.cpp
+++ b/src/terminalconfig.cpp
@@ -8,7 +8,7 @@
#include "properties.h"
#include "termwidget.h"
-TerminalConfig::TerminalConfig(const QString & wdir, const QString & shell)
+TerminalConfig::TerminalConfig(const QString & wdir, const QStringList & shell)
{
m_workingDirectory = wdir;
m_shell = shell;
@@ -32,20 +32,20 @@ QString TerminalConfig::getWorkingDirectory()
return QTerminalApp::Instance()->getWorkingDirectory();
}
-QString TerminalConfig::getShell()
+QStringList TerminalConfig::getShell()
{
- if (!m_shell.trimmed().isEmpty())
- return m_shell.trimmed();
- if (!Properties::Instance()->shell.trimmed().isEmpty())
- return Properties::Instance()->shell.trimmed();
+ if (!m_shell.isEmpty())
+ return m_shell;
+ if (!Properties::Instance()->shell.isEmpty())
+ return Properties::Instance()->shell;
QByteArray envShell = qgetenv("SHELL");
if (envShell.constData() != nullptr)
{
QString shellString = QString::fromLocal8Bit(envShell).trimmed();
if (!shellString.isEmpty())
- return shellString;
+ return QStringList{shellString};
}
- return QString();
+ return QStringList();
}
void TerminalConfig::setWorkingDirectory(const QString &val)
@@ -53,7 +53,7 @@ void TerminalConfig::setWorkingDirectory(const QString &val)
m_workingDirectory = val;
}
-void TerminalConfig::setShell(const QString &val)
+void TerminalConfig::setShell(const QStringList &val)
{
m_shell = val;
}
@@ -87,16 +87,23 @@ static QString variantToString(const QVariant& variant, QString &defaultVal)
return defaultVal;
}
+static QStringList variantToStringList(const QVariant& variant, QStringList &defaultVal)
+{
+ if (variant.type() == QVariant::StringList)
+ return qvariant_cast(variant);
+ return defaultVal;
+}
+
TerminalConfig TerminalConfig::fromDbus(const QHash &termArgs)
{
QString wdir = QString();
- QString shell(Properties::Instance()->shell);
+ QStringList shell(Properties::Instance()->shell);
if (termArgs.contains(QLatin1String(DBUS_ARG_WORKDIR)))
{
wdir = variantToString(termArgs[QLatin1String(DBUS_ARG_WORKDIR)], wdir);
}
if (termArgs.contains(QLatin1String(DBUS_ARG_SHELL))) {
- shell = variantToString(termArgs[QLatin1String(DBUS_ARG_SHELL)], shell);
+ shell = variantToStringList(termArgs[QLatin1String(DBUS_ARG_SHELL)], shell);
}
return TerminalConfig(wdir, shell);
}
diff --git a/src/terminalconfig.h b/src/terminalconfig.h
index 5823fae8..4afd18fc 100644
--- a/src/terminalconfig.h
+++ b/src/terminalconfig.h
@@ -11,15 +11,15 @@ class TermWidget;
class TerminalConfig
{
public:
- TerminalConfig(const QString & wdir, const QString & shell);
+ TerminalConfig(const QString & wdir, const QStringList & shell);
TerminalConfig(const TerminalConfig &cfg);
TerminalConfig();
QString getWorkingDirectory();
- QString getShell();
+ QStringList getShell();
void setWorkingDirectory(const QString &val);
- void setShell(const QString &val);
+ void setShell(const QStringList &val);
void provideCurrentDirectory(const QString &val);
#ifdef HAVE_QDBUS
@@ -29,10 +29,10 @@ class TerminalConfig
private:
- // True when
+ // True when
QString m_currentDirectory;
QString m_workingDirectory;
- QString m_shell;
+ QStringList m_shell;
};
-#endif
\ No newline at end of file
+#endif
diff --git a/src/termwidget.cpp b/src/termwidget.cpp
index f2c48613..f4536148 100644
--- a/src/termwidget.cpp
+++ b/src/termwidget.cpp
@@ -55,16 +55,13 @@ TermWidgetImpl::TermWidgetImpl(TerminalConfig &cfg, QWidget * parent)
setWorkingDirectory(cfg.getWorkingDirectory());
- QString shell = cfg.getShell();
+ QStringList shell = cfg.getShell();
if (!shell.isEmpty())
{
- //qDebug() << "Shell program:" << shell;
- QStringList parts = shell.split(QRegExp(QStringLiteral("\\s+")), Qt::SkipEmptyParts);
- //qDebug() << parts;
- setShellProgram(parts.at(0));
- parts.removeAt(0);
- if (parts.count())
- setArgs(parts);
+ setShellProgram(shell.at(0));
+ shell.removeAt(0);
+ if (!shell.isEmpty())
+ setArgs(shell);
}
setMotionAfterPasting(Properties::Instance()->m_motionAfterPaste);
diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt
new file mode 100644
index 00000000..5f332a04
--- /dev/null
+++ b/test/CMakeLists.txt
@@ -0,0 +1,7 @@
+qt5_wrap_cpp(QTERM_TEST_MOC qterminal_test.h)
+add_executable(qterminal_test
+ qterminal_test.cpp
+ ${CMAKE_SOURCE_DIR}/src/qterminalutils.cpp
+ ${QTERM_TEST_MOC})
+target_link_libraries(qterminal_test Qt5::Test)
+add_test(NAME qterminal_test COMMAND qterminal_test)
diff --git a/test/qterminal_test.cpp b/test/qterminal_test.cpp
new file mode 100644
index 00000000..c9c26513
--- /dev/null
+++ b/test/qterminal_test.cpp
@@ -0,0 +1,53 @@
+/***************************************************************************
+ * Copyright (C) 2022 by LXQt team *
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of the GNU General Public License as published by *
+ * the Free Software Foundation; either version 2 of the License, or *
+ * (at your option) any later version. *
+ * *
+ * This program 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 General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU General Public License *
+ * along with this program. If not, see . *
+ ***************************************************************************/
+
+#include "qterminal_test.h"
+
+#include "qterminalutils.h"
+
+#include
+
+// handy shortcut copied from liblxqt
+#ifndef QL1S
+#define QL1S(x) QLatin1String(x)
+#endif
+
+void QTerminalTest::testParseCommand()
+{
+ /* Common usage */
+ // qterminal -e 'fpad -s "PATH/ha ha"'
+ // qterminal -e "fpad -s \"PATH/ha ha\"" # \" is decoded as " by the shell
+ QCOMPARE(parse_command(QL1S(R"(fpad -s "PATH/ha ha")")),
+ QStringList() << QL1S("fpad") << QL1S("-s") << QL1S("PATH/ha ha"));
+ // qterminal -e "fpad -s 'PATH/ha ha'"
+ QCOMPARE(parse_command(QL1S(R"(fpad -s 'PATH/ha ha')")),
+ QStringList() << QL1S("fpad") << QL1S("-s") << QL1S("PATH/ha ha"));
+ // qterminal -e 'fpad -s PATH/ha\ ha'
+ // qterminal -e "fpad -s PATH/ha\ ha"
+ QCOMPARE(parse_command(QL1S(R"(fpad -s PATH/ha\ ha)")),
+ QStringList() << QL1S("fpad") << QL1S("-s") << QL1S("PATH/ha ha"));
+
+ /* Uncommon usage */
+ // qterminal -e 'fpad -s \"PATH/ha ha\"'
+ QCOMPARE(parse_command(QL1S(R"(fpad -s \"PATH/ha ha\")")),
+ QStringList() << QL1S("fpad") << QL1S("-s") << QL1S("\\\"PATH/ha") << QL1S("ha\\\""));
+ // qterminal -e 'fpad -s "PATH/ha\ ha"'
+ QCOMPARE(parse_command(QL1S(R"(fpad -s "PATH/ha\ ha")")),
+ QStringList() << QL1S("fpad") << QL1S("-s") << QL1S("PATH/ha ha"));
+}
+
+QTEST_MAIN(QTerminalTest)
diff --git a/test/qterminal_test.h b/test/qterminal_test.h
new file mode 100644
index 00000000..9c64ec98
--- /dev/null
+++ b/test/qterminal_test.h
@@ -0,0 +1,32 @@
+/***************************************************************************
+ * Copyright (C) 2022 by LXQt team *
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of the GNU General Public License as published by *
+ * the Free Software Foundation; either version 2 of the License, or *
+ * (at your option) any later version. *
+ * *
+ * This program 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 General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU General Public License *
+ * along with this program. If not, see . *
+ ***************************************************************************/
+
+#ifndef QTERMINAL_TEST_H
+#define QTERMINAL_TEST_H
+
+#include
+
+class QTerminalTest : public QObject
+{
+ Q_OBJECT
+
+ // Each private slot is a test function
+private Q_SLOTS:
+ void testParseCommand();
+};
+
+#endif