diff --git a/README.md b/README.md index b5913529..89d2f3e7 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,28 @@ If you want to get a hold of the latest version, just go to the Releases page an Alternatively, most distributions such as Ubuntu, Fedora or Arch already package cool-retro-term in their official repositories. +## Command line + +cool-retro-term can run a command on startup with `-e`: + +```sh +cool-retro-term -e top +``` + +It can also open a telnet connection on startup: + +```sh +cool-retro-term --telnet-host example.com --telnet-port 23 +``` + +For telnet servers that prompt for credentials, cool-retro-term can send a username and password when it detects login and password prompts: + +```sh +cool-retro-term --telnet-host example.com --telnet-user myuser --telnet-password mypass +``` + +The telnet options use the system `telnet` command, so `telnet` must be installed and available in `PATH`. Passing passwords on the command line can expose them through shell history or process listings. + ## Building Check out the wiki and follow the instructions on how to build it on [Linux](https://github.com/Swordfish90/cool-retro-term/wiki/Build-Instructions-(Linux)) and [macOS](https://github.com/Swordfish90/cool-retro-term/wiki/Build-Instructions-(macOS)). diff --git a/app/main.cpp b/app/main.cpp index ee793213..af66d1c6 100644 --- a/app/main.cpp +++ b/app/main.cpp @@ -3,6 +3,7 @@ #include #include +#include #include @@ -31,7 +32,7 @@ QString getNamedArgument(QStringList args, QString name, QString defaultName) { int index = args.indexOf(name); - return (index != -1) ? args[index + 1] : QString(defaultName); + return (index != -1 && index + 1 < args.size()) ? args[index + 1] : QString(defaultName); } QString getNamedArgument(QStringList args, QString name) @@ -64,12 +65,16 @@ int main(int argc, char *argv[]) if (argc>1 && (!strcmp(argv[1],"-h") || !strcmp(argv[1],"--help"))) { QTextStream cout(stdout, QIODevice::WriteOnly); - cout << "Usage: " << argv[0] << " [--default-settings] [--workdir ] [--program ] [-p|--profile ] [--fullscreen] [-h|--help]" << Qt::endl; + cout << "Usage: " << argv[0] << " [--default-settings] [--workdir ] [--program ] [-p|--profile ] [--fullscreen] [--telnet-host ] [--telnet-port ] [--telnet-user ] [--telnet-password ] [-h|--help]" << Qt::endl; cout << " --default-settings Run cool-retro-term with the default settings" << Qt::endl; cout << " --workdir Change working directory to 'dir'" << Qt::endl; cout << " -e Command to execute. This option will catch all following arguments, so use it as the last option." << Qt::endl; cout << " --fullscreen Run cool-retro-term in fullscreen." << Qt::endl; cout << " -p|--profile Run cool-retro-term with the given profile." << Qt::endl; + cout << " --telnet-host Connect to a host with telnet on startup." << Qt::endl; + cout << " --telnet-port Port for --telnet-host. Defaults to telnet's default port." << Qt::endl; + cout << " --telnet-user Send this username when a login prompt is detected." << Qt::endl; + cout << " --telnet-password Send this password when a password prompt is detected." << Qt::endl; cout << " -h|--help Print this help." << Qt::endl; cout << " --verbose Print additional information such as profiles and settings." << Qt::endl; return 0; @@ -90,12 +95,19 @@ int main(int argc, char *argv[]) app.setOrganizationDomain(QStringLiteral("cool-retro-term")); app.setApplicationVersion(appVersion); + QStringList args = app.arguments(); + const bool hasStartupCommand = args.contains(QStringLiteral("-e")) || args.contains(QStringLiteral("--telnet-host")); + KDSingleApplication singleApp(QStringLiteral("cool-retro-term")); if (!singleApp.isPrimaryInstance()) { - if (singleApp.sendMessage("new-window")) - return 0; - qWarning() << "KDSingleApplication: primary not reachable, continuing as independent instance."; + if (hasStartupCommand) { + qInfo() << "KDSingleApplication: startup command requested, continuing as independent instance."; + } else { + if (singleApp.sendMessage("new-window")) + return 0; + qWarning() << "KDSingleApplication: primary not reachable, continuing as independent instance."; + } } QQmlApplicationEngine engine; @@ -114,18 +126,33 @@ int main(int argc, char *argv[]) #endif // Manage command line arguments from the cpp side - QStringList args = app.arguments(); - // Manage default command QStringList cmdList; + const QString telnetHost = getNamedArgument(args, "--telnet-host"); + const QString telnetPort = getNamedArgument(args, "--telnet-port"); + const QString telnetUser = getNamedArgument(args, "--telnet-user"); + const QString telnetPassword = getNamedArgument(args, "--telnet-password"); + if (args.contains("-e")) { cmdList << args.mid(args.indexOf("-e") + 1); + } else if (!telnetHost.isEmpty()) { + cmdList << QStringLiteral("telnet") << telnetHost; + if (!telnetPort.isEmpty()) { + cmdList << telnetPort; + } } + + QVariantMap telnetLogin; + telnetLogin.insert(QStringLiteral("enabled"), !telnetHost.isEmpty() && (!telnetUser.isEmpty() || !telnetPassword.isEmpty())); + telnetLogin.insert(QStringLiteral("username"), telnetUser); + telnetLogin.insert(QStringLiteral("password"), telnetPassword); + QVariant command(cmdList.empty() ? QVariant() : cmdList[0]); QVariant commandArgs(cmdList.size() <= 1 ? QVariant() : QVariant(cmdList.mid(1))); engine.rootContext()->setContextProperty("appVersion", appVersion); engine.rootContext()->setContextProperty("defaultCmd", command); engine.rootContext()->setContextProperty("defaultCmdArgs", commandArgs); + engine.rootContext()->setContextProperty("telnetLogin", telnetLogin); engine.rootContext()->setContextProperty("workdir", getNamedArgument(args, "--workdir", QDir::currentPath())); engine.rootContext()->setContextProperty("fileIO", &fileIO); diff --git a/app/qml/PreprocessedTerminal.qml b/app/qml/PreprocessedTerminal.qml index e8e8d2e2..d99e385c 100644 --- a/app/qml/PreprocessedTerminal.qml +++ b/app/qml/PreprocessedTerminal.qml @@ -41,6 +41,8 @@ Item{ property alias title: ksession.title property alias kterminal: kterminal property bool isActive: false + property int telnetLoginState: 0 + property string telnetPromptBuffer: "" property size terminalSize: kterminal.terminalSize property size fontMetrics: kterminal.fontMetrics @@ -125,6 +127,10 @@ Item{ onFinished: { terminalContainer.sessionFinished() } + + onReceivedData: function(text) { + kterminal.handleTelnetLogin(text) + } } QMLTermScrollbar { @@ -186,6 +192,30 @@ Item{ ksession.startShellProgram(); forceActiveFocus(); } + + function handleTelnetLogin(text) { + if (!telnetLogin.enabled || telnetLoginState >= 2) { + return; + } + + telnetPromptBuffer = (telnetPromptBuffer + text).slice(-512).toLowerCase(); + + if (telnetLoginState === 0 + && telnetLogin.username + && /(login|username|user)[^a-z0-9]*:\s*$/.test(telnetPromptBuffer)) { + ksession.sendText(telnetLogin.username + "\r"); + telnetLoginState = 1; + telnetPromptBuffer = ""; + return; + } + + if (telnetLogin.password + && /password[^a-z0-9]*:\s*$/.test(telnetPromptBuffer)) { + ksession.sendText(telnetLogin.password + "\r"); + telnetLoginState = 2; + telnetPromptBuffer = ""; + } + } Component.onCompleted: { appSettings.fontManager.terminalFontChanged.connect(handleFontChanged); appSettings.fontManager.refresh() diff --git a/qmltermwidget b/qmltermwidget index 8913504f..7bda3ec3 160000 --- a/qmltermwidget +++ b/qmltermwidget @@ -1 +1 @@ -Subproject commit 8913504fa2ebd220ebe7c680c32954e1b3c035c5 +Subproject commit 7bda3ec3e388e85eaaef361eb887485249fde98a