diff --git a/src/libs/utils/qtcprocess.cpp b/src/libs/utils/qtcprocess.cpp index aed8955de2c..162112d88b5 100644 --- a/src/libs/utils/qtcprocess.cpp +++ b/src/libs/utils/qtcprocess.cpp @@ -34,6 +34,7 @@ #include "processreaper.h" #include "qtcassert.h" #include "stringutils.h" +#include "terminalprocess_p.h" #include #include @@ -226,6 +227,9 @@ public: virtual void setWorkingDirectory(const QString &dir) = 0; virtual void start(const QString &program, const QStringList &arguments, const QByteArray &writeData) = 0; + virtual void customStart(const CommandLine &command, const FilePath &workingDirectory, + const Environment &environment) { QTC_CHECK(false); } + virtual bool isCustomStart() const { return false; } virtual void terminate() = 0; virtual void kill() = 0; virtual void close() = 0; @@ -255,6 +259,9 @@ public: void setUseTerminal(bool on) { m_useTerminal = on; } bool useTerminal() const { return m_useTerminal; } + void setAbortOnMetaChars(bool abort) { m_abortOnMetaChars = abort; } + bool isAbortOnMetaChars() const { return m_abortOnMetaChars; } + void setRunAsRoot(bool on) { m_runAsRoot = on; } bool runAsRoot() const { return m_runAsRoot; } @@ -279,9 +286,75 @@ private: bool m_lowPriority = false; bool m_unixTerminalDisabled = false; bool m_useTerminal = false; + bool m_abortOnMetaChars = true; bool m_runAsRoot = false; }; +class TerminalImpl : public ProcessInterface +{ +public: + TerminalImpl(QObject *parent, QtcProcess::ProcessImpl processImpl, + QtcProcess::TerminalMode terminalMode) + : ProcessInterface(parent, ProcessMode::Reader) + , m_terminal(this, processImpl, terminalMode) + { + connect(&m_terminal, &Internal::TerminalProcess::started, + this, &ProcessInterface::started); + connect(&m_terminal, &Internal::TerminalProcess::finished, + this, &ProcessInterface::finished); + connect(&m_terminal, &Internal::TerminalProcess::errorOccurred, + this, &ProcessInterface::errorOccurred); + } + ~TerminalImpl() override + { + } + + QByteArray readAllStandardOutput() override { QTC_CHECK(false); return {}; } + QByteArray readAllStandardError() override { QTC_CHECK(false); return {}; } + + void setProcessEnvironment(const QProcessEnvironment &environment) override { QTC_CHECK(false); } + void setWorkingDirectory(const QString &dir) override { QTC_CHECK(false); } + void start(const QString &program, const QStringList &arguments, const QByteArray &writeData) override + { QTC_CHECK(false); } + void customStart(const CommandLine &command, const FilePath &workingDirectory, + const Environment &environment) override + { + m_terminal.setRunAsRoot(runAsRoot()); + m_terminal.setAbortOnMetaChars(isAbortOnMetaChars()); + m_terminal.setCommand(command); + m_terminal.setWorkingDirectory(workingDirectory); + m_terminal.setEnvironment(environment); + m_terminal.start(); + } + bool isCustomStart() const override { return true; } + void terminate() override { m_terminal.stopProcess(); } + void kill() override { m_terminal.stopProcess(); } + void close() override { m_terminal.stopProcess(); } + qint64 write(const QByteArray &data) override { QTC_CHECK(false); return -1; } + + void setStandardInputFile(const QString &fileName) override { Q_UNUSED(fileName) QTC_CHECK(false); } + // intentionally no-op without an assert + void setProcessChannelMode(QProcess::ProcessChannelMode mode) override { Q_UNUSED(mode) } + + QString program() const override { QTC_CHECK(false); return {}; } + QProcess::ProcessError error() const override { return m_terminal.error(); } + QProcess::ProcessState state() const override { return m_terminal.state(); } + qint64 processId() const override { return m_terminal.processId(); } + int exitCode() const override { return m_terminal.exitCode(); } + QProcess::ExitStatus exitStatus() const override { return m_terminal.exitStatus(); } + QString errorString() const override { return m_terminal.errorString(); } + void setErrorString(const QString &str) override { QTC_CHECK(false); } + + // intentionally no-op without an assert + bool waitForStarted(int msecs) override { return false; } + bool waitForReadyRead(int msecs) override { QTC_CHECK(false); return false; } + // intentionally no-op without an assert + bool waitForFinished(int msecs) override { return false; } + +private: + Internal::TerminalProcess m_terminal; +}; + class QProcessImpl : public ProcessInterface { public: @@ -455,7 +528,7 @@ private: quintptr token() const { return m_token; } const uint m_token = 0; - // Lives in launcher's thread. + // Lives in caller's thread. CallerHandle *m_handle = nullptr; }; @@ -465,8 +538,10 @@ void ProcessLauncherImpl::cancel() } static ProcessInterface *newProcessInstance(QObject *parent, QtcProcess::ProcessImpl processImpl, - ProcessMode mode) + ProcessMode mode, QtcProcess::TerminalMode terminalMode) { + if (terminalMode != QtcProcess::TerminalOff) + return new TerminalImpl(parent, processImpl, terminalMode); if (processImpl == QtcProcess::QProcessImpl) return new QProcessImpl(parent, mode); return new ProcessLauncherImpl(parent, mode); @@ -483,10 +558,11 @@ public: explicit QtcProcessPrivate(QtcProcess *parent, QtcProcess::ProcessImpl processImpl, - ProcessMode processMode) + ProcessMode processMode, + QtcProcess::TerminalMode terminalMode) : QObject(parent) , q(parent) - , m_process(newProcessInstance(parent, processImpl, processMode)) + , m_process(newProcessInstance(parent, processImpl, processMode, terminalMode)) , m_processMode(processMode) { connect(m_process, &ProcessInterface::started, @@ -526,6 +602,68 @@ public: return filePath.searchInPath(); } + void defaultStart() + { + clearForRun(); + + if (m_commandLine.executable().needsDevice()) { + QTC_ASSERT(s_deviceHooks.startProcessHook, return); + s_deviceHooks.startProcessHook(*q); + return; + } + + if (processLog().isDebugEnabled()) { + static int n = 0; + qCDebug(processLog) << "STARTING PROCESS: " << ++n << " " << m_commandLine.toUserOutput(); + } + + Environment env; + if (m_haveEnv) { + if (m_environment.size() == 0) + qWarning("QtcProcess::start: Empty environment set when running '%s'.", + qPrintable(m_commandLine.executable().toString())); + env = m_environment; + } else { + env = Environment::systemEnvironment(); + } + m_process->setProcessEnvironment(env.toProcessEnvironment()); + m_process->setWorkingDirectory(m_workingDirectory.path()); + + QString command; + ProcessArgs arguments; + const bool success = ProcessArgs::prepareCommand(m_commandLine, &command, &arguments, &env, + &m_workingDirectory); + + if (m_commandLine.executable().osType() == OsTypeWindows) { + QString args; + if (m_useCtrlCStub) { + if (m_process->isLowPriority()) + ProcessArgs::addArg(&args, "-nice"); + ProcessArgs::addArg(&args, QDir::toNativeSeparators(command)); + command = QCoreApplication::applicationDirPath() + + QLatin1String("/qtcreator_ctrlc_stub.exe"); + } else if (m_process->isLowPriority()) { + m_process->setBelowNormalPriority(); + } + ProcessArgs::addArgs(&args, arguments.toWindowsArgs()); +#ifdef Q_OS_WIN + m_process->setNativeArguments(args); +#endif + // Note: Arguments set with setNativeArgs will be appended to the ones + // passed with start() below. + start(command, QStringList(), m_writeData); + } else { + if (!success) { + q->setErrorString(tr("Error in command line.")); + // Should be FailedToStart, but we cannot set the process error from the outside, + // so it would be inconsistent. + emit q->errorOccurred(QProcess::UnknownError); + return; + } + start(command, arguments.toUnixArgs(), m_writeData); + } + } + void start(const QString &program, const QStringList &arguments, const QByteArray &writeData) { const FilePath programFilePath = resolve(m_workingDirectory, FilePath::fromString(program)); @@ -607,8 +745,9 @@ static QtcProcess::ProcessImpl defaultProcessImpl() return QtcProcess::ProcessLauncherImpl; } -QtcProcess::QtcProcess(ProcessImpl processImpl, ProcessMode processMode, QObject *parent) - : QObject(parent), d(new QtcProcessPrivate(this, processImpl, processMode)) +QtcProcess::QtcProcess(ProcessImpl processImpl, ProcessMode processMode, TerminalMode terminalMode, + QObject *parent) + : QObject(parent), d(new QtcProcessPrivate(this, processImpl, processMode, terminalMode)) { static int qProcessExitStatusMeta = qRegisterMetaType(); static int qProcessProcessErrorMeta = qRegisterMetaType(); @@ -617,13 +756,16 @@ QtcProcess::QtcProcess(ProcessImpl processImpl, ProcessMode processMode, QObject } QtcProcess::QtcProcess(ProcessImpl processImpl, QObject *parent) - : QtcProcess(processImpl, ProcessMode::Reader, parent) {} + : QtcProcess(processImpl, ProcessMode::Reader, TerminalMode::TerminalOff, parent) {} QtcProcess::QtcProcess(ProcessMode processMode, QObject *parent) - : QtcProcess(defaultProcessImpl(), processMode, parent) {} + : QtcProcess(defaultProcessImpl(), processMode, TerminalMode::TerminalOff, parent) {} + +QtcProcess::QtcProcess(TerminalMode terminalMode, QObject *parent) + : QtcProcess(defaultProcessImpl(), ProcessMode::Reader, terminalMode, parent) {} QtcProcess::QtcProcess(QObject *parent) - : QtcProcess(defaultProcessImpl(), ProcessMode::Reader, parent) {} + : QtcProcess(defaultProcessImpl(), ProcessMode::Reader, TerminalMode::TerminalOff, parent) {} QtcProcess::~QtcProcess() { @@ -692,65 +834,10 @@ void QtcProcess::setUseCtrlCStub(bool enabled) void QtcProcess::start() { - d->clearForRun(); - - if (d->m_commandLine.executable().needsDevice()) { - QTC_ASSERT(s_deviceHooks.startProcessHook, return); - s_deviceHooks.startProcessHook(*this); - return; - } - - if (processLog().isDebugEnabled()) { - static int n = 0; - qCDebug(processLog) << "STARTING PROCESS: " << ++n << " " << d->m_commandLine.toUserOutput(); - } - - Environment env; - if (d->m_haveEnv) { - if (d->m_environment.size() == 0) - qWarning("QtcProcess::start: Empty environment set when running '%s'.", - qPrintable(d->m_commandLine.executable().toString())); - env = d->m_environment; - } else { - env = Environment::systemEnvironment(); - } - d->m_process->setProcessEnvironment(env.toProcessEnvironment()); - - d->m_process->setWorkingDirectory(d->m_workingDirectory.path()); - - QString command; - ProcessArgs arguments; - bool success = ProcessArgs::prepareCommand(d->m_commandLine, &command, &arguments, &env, - &d->m_workingDirectory); - - if (d->m_commandLine.executable().osType() == OsTypeWindows) { - QString args; - if (d->m_useCtrlCStub) { - if (d->m_process->isLowPriority()) - ProcessArgs::addArg(&args, "-nice"); - ProcessArgs::addArg(&args, QDir::toNativeSeparators(command)); - command = QCoreApplication::applicationDirPath() - + QLatin1String("/qtcreator_ctrlc_stub.exe"); - } else if (d->m_process->isLowPriority()) { - d->m_process->setBelowNormalPriority(); - } - ProcessArgs::addArgs(&args, arguments.toWindowsArgs()); -#ifdef Q_OS_WIN - d->m_process->setNativeArguments(args); -#endif - // Note: Arguments set with setNativeArgs will be appended to the ones - // passed with start() below. - d->start(command, QStringList(), d->m_writeData); - } else { - if (!success) { - setErrorString(tr("Error in command line.")); - // Should be FailedToStart, but we cannot set the process error from the outside, - // so it would be inconsistent. - emit errorOccurred(QProcess::UnknownError); - return; - } - d->start(command, arguments.toUnixArgs(), d->m_writeData); - } + if (d->m_process->isCustomStart()) + d->m_process->customStart(d->m_commandLine, d->m_workingDirectory, d->m_environment); + else + d->defaultStart(); } #ifdef Q_OS_WIN @@ -824,6 +911,11 @@ bool QtcProcess::useTerminal() const return d->m_process->useTerminal(); } +void QtcProcess::setAbortOnMetaChars(bool abort) +{ + d->m_process->setAbortOnMetaChars(abort); +} + void QtcProcess::setRunAsRoot(bool on) { d->m_process->setRunAsRoot(on); diff --git a/src/libs/utils/qtcprocess.h b/src/libs/utils/qtcprocess.h index a17f46a06f9..652033b9c92 100644 --- a/src/libs/utils/qtcprocess.h +++ b/src/libs/utils/qtcprocess.h @@ -65,9 +65,19 @@ public: ProcessLauncherImpl }; - QtcProcess(ProcessImpl processImpl, ProcessMode processMode, QObject *parent = nullptr); + enum TerminalMode { + TerminalOff, + TerminalRun, + TerminalDebug, + TerminalSuspend, + TerminalOn = TerminalRun // default mode for ON + }; + + QtcProcess(ProcessImpl processImpl, ProcessMode processMode, TerminalMode terminalMode, + QObject *parent = nullptr); QtcProcess(ProcessImpl processImpl, QObject *parent = nullptr); QtcProcess(ProcessMode processMode, QObject *parent = nullptr); + QtcProcess(TerminalMode terminalMode, QObject *parent = nullptr); QtcProcess(QObject *parent = nullptr); ~QtcProcess(); @@ -108,6 +118,8 @@ public: void setUseTerminal(bool on); bool useTerminal() const; + void setAbortOnMetaChars(bool abort); + void start(); void terminate(); void interrupt(); @@ -128,7 +140,6 @@ public: void setTimeOutMessageBoxEnabled(bool); void setExitCodeInterpreter(const std::function &interpreter); - // FIXME: This is currently only used in run(), not in start() void setWriteData(const QByteArray &writeData); void setStdOutCallback(const std::function &callback); diff --git a/src/libs/utils/terminalprocess.cpp b/src/libs/utils/terminalprocess.cpp index d362743a2f9..30d02fed72e 100644 --- a/src/libs/utils/terminalprocess.cpp +++ b/src/libs/utils/terminalprocess.cpp @@ -63,17 +63,20 @@ namespace Utils { namespace Internal { -static QString modeOption(TerminalProcess::Mode m) +static QString modeOption(QtcProcess::TerminalMode m) { switch (m) { - case TerminalProcess::Debug: + case QtcProcess::TerminalRun: + return QLatin1String("run"); + case QtcProcess::TerminalDebug: return QLatin1String("debug"); - case TerminalProcess::Suspend: + case QtcProcess::TerminalSuspend: return QLatin1String("suspend"); - case TerminalProcess::Run: + case QtcProcess::TerminalOff: + QTC_CHECK(false); break; } - return QLatin1String("run"); + return {}; } static QString msgCommChannelFailed(const QString &error) @@ -120,9 +123,13 @@ static QString msgCannotExecute(const QString & p, const QString &why) class TerminalProcessPrivate { public: - TerminalProcessPrivate() = default; + TerminalProcessPrivate(QObject *parent, QtcProcess::ProcessImpl processImpl, + QtcProcess::TerminalMode terminalMode) + : m_terminalMode(terminalMode) + , m_process(processImpl, parent) + {} - TerminalProcess::Mode m_mode = TerminalProcess::Run; + const QtcProcess::TerminalMode m_terminalMode; FilePath m_workingDir; Environment m_environment; qint64 m_processId = 0; @@ -153,8 +160,9 @@ public: #endif }; -TerminalProcess::TerminalProcess(QObject *parent) : - QObject(parent), d(new TerminalProcessPrivate) +TerminalProcess::TerminalProcess(QObject *parent, QtcProcess::ProcessImpl processImpl, + QtcProcess::TerminalMode terminalMode) : + QObject(parent), d(new TerminalProcessPrivate(this, processImpl, terminalMode)) { connect(&d->m_stubServer, &QLocalServer::newConnection, this, &TerminalProcess::stubConnectionAvailable); @@ -301,7 +309,7 @@ void TerminalProcess::start() QString pcmd; QString pargs; - if (d->m_mode != Run) { // The debugger engines already pre-process the arguments. + if (d->m_terminalMode != QtcProcess::TerminalRun) { // The debugger engines already pre-process the arguments. pcmd = d->m_commandLine.executable().toString(); pargs = d->m_commandLine.arguments(); } else { @@ -369,7 +377,7 @@ void TerminalProcess::start() workDir.append(QLatin1Char('\\')); QStringList stubArgs; - stubArgs << modeOption(d->m_mode) + stubArgs << modeOption(d->m_terminalMode) << d->m_stubServer.fullServerName() << workDir << (d->m_tempFile ? d->m_tempFile->fileName() : QString()) @@ -415,7 +423,7 @@ void TerminalProcess::start() emitError(QProcess::FailedToStart, tr("Quoting error in command.")); return; } - if (d->m_mode == Debug) { + if (d->m_terminalMode == QtcProcess::TerminalDebug) { // FIXME: QTCREATORBUG-2809 emitError(QProcess::FailedToStart, tr("Debugging complex shell commands in a terminal" " is currently not supported.")); @@ -475,7 +483,7 @@ void TerminalProcess::start() allArgs << "sudo" << "-A"; allArgs << stubPath - << modeOption(d->m_mode) + << modeOption(d->m_terminalMode) << d->m_stubServer.fullServerName() << msgPromptToClose() << workingDirectory().path() @@ -517,7 +525,7 @@ void TerminalProcess::finish(int exitCode, QProcess::ExitStatus exitStatus) d->m_processId = 0; d->m_exitCode = exitCode; d->m_appStatus = exitStatus; - emit finished(); + emit finished(exitCode, exitStatus); } void TerminalProcess::kickoffProcess() @@ -600,6 +608,16 @@ bool TerminalProcess::isRunning() const #endif } +QProcess::ProcessState TerminalProcess::state() const +{ +#ifdef Q_OS_WIN + return (d->m_pid != nullptr) ? QProcess::Running : QProcess::NotRunning; +#else + return (d->m_stubSocket && d->m_stubSocket->isOpen()) + ? QProcess::Running : d->m_process.state(); +#endif +} + QString TerminalProcess::stubServerListen() { #ifdef Q_OS_WIN @@ -792,16 +810,6 @@ void TerminalProcess::cleanupStub() #endif } -void TerminalProcess::setMode(Mode m) -{ - d->m_mode = m; -} - -TerminalProcess::Mode TerminalProcess::mode() const -{ - return d->m_mode; -} - qint64 TerminalProcess::processId() const { return d->m_processId; diff --git a/src/libs/utils/terminalprocess_p.h b/src/libs/utils/terminalprocess_p.h index a920799c79a..612981f22c0 100644 --- a/src/libs/utils/terminalprocess_p.h +++ b/src/libs/utils/terminalprocess_p.h @@ -25,7 +25,7 @@ #pragma once -#include +#include "qtcprocess.h" namespace Utils { @@ -38,25 +38,21 @@ namespace Internal { class TerminalProcess : public QObject { Q_OBJECT - public: - enum Mode { Run, Debug, Suspend }; - - explicit TerminalProcess(QObject *parent = nullptr); + explicit TerminalProcess(QObject *parent, QtcProcess::ProcessImpl processImpl, + QtcProcess::TerminalMode terminalMode); ~TerminalProcess() override; void setCommand(const CommandLine &command); const CommandLine &commandLine() const; - void setAbortOnMetaChars(bool abort); // used only in sshDeviceProcess - void setWorkingDirectory(const FilePath &dir); FilePath workingDirectory() const; void setEnvironment(const Environment &env); const Environment &environment() const; - void setRunAsRoot(bool on); // OK, however, unused currently in QtcProcess, non-windows only + void setRunAsRoot(bool on); QProcess::ProcessError error() const; QString errorString() const; @@ -64,28 +60,25 @@ public: void start(); void stopProcess(); -public: - void setMode(Mode m); // only debugger terminal - Mode mode() const; // no usages - // OK, however, impl looks a bit different (!= NotRunning vs == Running). // Most probably changing it into (== Running) should be OK. - bool isRunning() const; // This reflects the state of the console+stub + bool isRunning() const; + + QProcess::ProcessState state() const; qint64 processId() const; - - void kickoffProcess(); // only debugger terminal, only non-windows - void interruptProcess(); // only debugger terminal, only non-windows - - qint64 applicationMainThreadID() const; // only debugger terminal, only windows (-1 otherwise) - int exitCode() const; QProcess::ExitStatus exitStatus() const; + void setAbortOnMetaChars(bool abort); // used only in sshDeviceProcess + void kickoffProcess(); // only debugger terminal, only non-windows + void interruptProcess(); // only debugger terminal, only non-windows + qint64 applicationMainThreadID() const; // only debugger terminal, only windows (-1 otherwise) + static bool startTerminalEmulator(const QString &workingDir, const Environment &env); signals: void started(); - void finished(); + void finished(int exitCode, QProcess::ExitStatus status); void errorOccurred(QProcess::ProcessError error); private: diff --git a/src/plugins/remotelinux/linuxdevice.cpp b/src/plugins/remotelinux/linuxdevice.cpp index 554ea8a13a4..91c58bff1ea 100644 --- a/src/plugins/remotelinux/linuxdevice.cpp +++ b/src/plugins/remotelinux/linuxdevice.cpp @@ -207,6 +207,9 @@ public: bool start(const SshConnectionParameters ¶meters) { + // TODO: start here shared ssh connection if needed (take it from settings) + // connect to it + // wait for connected m_shell = new SshRemoteProcess("/bin/sh", parameters.connectionOptions(SshSettings::sshFilePath()) << parameters.host(), ProcessMode::Writer);