diff --git a/src/libs/3rdparty/libptyqt/conptyprocess.cpp b/src/libs/3rdparty/libptyqt/conptyprocess.cpp index d112f5e1533..c788e74c92a 100644 --- a/src/libs/3rdparty/libptyqt/conptyprocess.cpp +++ b/src/libs/3rdparty/libptyqt/conptyprocess.cpp @@ -123,6 +123,7 @@ bool ConPtyProcess::startProcess(const QString &executable, } m_shellPath = executable; + m_shellPath.replace('/', '\\'); m_size = QPair(cols, rows); //env @@ -134,6 +135,7 @@ bool ConPtyProcess::startProcess(const QString &executable, envBlock << L'\0'; std::wstring env = envBlock.str(); LPWSTR envArg = env.empty() ? nullptr : env.data(); + LPCWSTR workingDirPointer = workingDir.isEmpty() ? nullptr : workingDir.toStdWString().c_str(); QStringList exeAndArgs = arguments; exeAndArgs.prepend(m_shellPath); @@ -165,7 +167,7 @@ bool ConPtyProcess::startProcess(const QString &executable, FALSE, // Inherit handles EXTENDED_STARTUPINFO_PRESENT | CREATE_UNICODE_ENVIRONMENT, // Creation flags envArg, // Environment block - workingDir.toStdWString().c_str(), // Use parent's starting directory + workingDirPointer, // Use parent's starting directory &m_shellStartupInfo.StartupInfo, // Pointer to STARTUPINFO &m_shellProcessInformation) // Pointer to PROCESS_INFORMATION ? S_OK @@ -264,11 +266,13 @@ bool ConPtyProcess::kill() if (INVALID_HANDLE_VALUE != m_hPipeIn) CloseHandle(m_hPipeIn); - m_readThread->requestInterruption(); - if (!m_readThread->wait(1000)) - m_readThread->terminate(); - m_readThread->deleteLater(); - m_readThread = nullptr; + if (m_readThread) { + m_readThread->requestInterruption(); + if (!m_readThread->wait(1000)) + m_readThread->terminate(); + m_readThread->deleteLater(); + m_readThread = nullptr; + } delete m_shellCloseWaitNotifier; m_shellCloseWaitNotifier = nullptr; diff --git a/src/libs/3rdparty/libptyqt/unixptyprocess.cpp b/src/libs/3rdparty/libptyqt/unixptyprocess.cpp index 7cb8237a605..e049a2abb92 100644 --- a/src/libs/3rdparty/libptyqt/unixptyprocess.cpp +++ b/src/libs/3rdparty/libptyqt/unixptyprocess.cpp @@ -182,6 +182,7 @@ bool UnixPtyProcess::startProcess(const QString &shellPath, QObject::connect(&m_shellProcess, &QProcess::finished, &m_shellProcess, [this](int exitCode) { m_exitCode = exitCode; emit m_shellProcess.aboutToClose(); + m_readMasterNotify->disconnect(); }); QStringList defaultVars; @@ -216,7 +217,8 @@ bool UnixPtyProcess::startProcess(const QString &shellPath, m_shellProcess.setProcessEnvironment(envFormat); m_shellProcess.setReadChannel(QProcess::StandardOutput); m_shellProcess.start(m_shellPath, arguments); - m_shellProcess.waitForStarted(); + if (!m_shellProcess.waitForStarted()) + return false; m_pid = m_shellProcess.processId(); diff --git a/src/libs/3rdparty/libptyqt/winptyprocess.cpp b/src/libs/3rdparty/libptyqt/winptyprocess.cpp index be3a0de609e..0509bb77c37 100644 --- a/src/libs/3rdparty/libptyqt/winptyprocess.cpp +++ b/src/libs/3rdparty/libptyqt/winptyprocess.cpp @@ -60,6 +60,7 @@ bool WinPtyProcess::startProcess(const QString &executable, } m_shellPath = executable; + m_shellPath.replace('/', '\\'); m_size = QPair(cols, rows); #ifdef PTYQT_DEBUG diff --git a/src/libs/3rdparty/winpty/src/CMakeLists.txt b/src/libs/3rdparty/winpty/src/CMakeLists.txt index 5763955e8d0..22b15111d4f 100644 --- a/src/libs/3rdparty/winpty/src/CMakeLists.txt +++ b/src/libs/3rdparty/winpty/src/CMakeLists.txt @@ -46,7 +46,6 @@ set(shared_sources # add_qtc_executable(winpty-agent - DESTINATION ${IDE_PLUGIN_PATH} INCLUDES include ${CMAKE_BINARY_DIR} DEFINES WINPTY_AGENT_ASSERT diff --git a/src/libs/utils/CMakeLists.txt b/src/libs/utils/CMakeLists.txt index 04f475afb7c..d6b6efb4f2d 100644 --- a/src/libs/utils/CMakeLists.txt +++ b/src/libs/utils/CMakeLists.txt @@ -257,7 +257,7 @@ extend_qtc_library(Utils CONDITION UNIX AND NOT APPLE extend_qtc_library(Utils CONDITION TARGET Qt::CorePrivate - DEPENDS Qt::CorePrivate + DEPENDS Qt::CorePrivate ptyqt DEFINES QTC_UTILS_WITH_FSENGINE SOURCES fsengine/fsengine_impl.cpp fsengine/fsengine_impl.h diff --git a/src/libs/utils/processenums.h b/src/libs/utils/processenums.h index 6ea37a2d37a..1c16f22dcb4 100644 --- a/src/libs/utils/processenums.h +++ b/src/libs/utils/processenums.h @@ -24,6 +24,7 @@ enum class ProcessImpl { enum class TerminalMode { Off, + Pty, Run, Debug, Suspend, diff --git a/src/libs/utils/qtcprocess.cpp b/src/libs/utils/qtcprocess.cpp index 42a29366bd9..4808764ddde 100644 --- a/src/libs/utils/qtcprocess.cpp +++ b/src/libs/utils/qtcprocess.cpp @@ -16,6 +16,9 @@ #include "threadutils.h" #include "utilstr.h" +#include +#include + #include #include #include @@ -304,6 +307,99 @@ private: QProcess *m_process = nullptr; }; +class PtyProcessImpl final : public DefaultImpl +{ +public: + ~PtyProcessImpl() { m_setup.m_ptyData.setResizeHandler({}); } + + qint64 write(const QByteArray &data) final + { + if (m_ptyProcess) + return m_ptyProcess->write(data); + return -1; + } + + void sendControlSignal(ControlSignal controlSignal) final + { + if (!m_ptyProcess) + return; + + switch (controlSignal) { + case ControlSignal::Terminate: + m_ptyProcess.reset(); + break; + case ControlSignal::Kill: + m_ptyProcess->kill(); + break; + default: + QTC_CHECK(false); + } + } + + void doDefaultStart(const QString &program, const QStringList &arguments) final + { + m_setup.m_ptyData.setResizeHandler([this](const QSize &size) { + if (m_ptyProcess) + m_ptyProcess->resize(size.width(), size.height()); + }); + m_ptyProcess.reset(PtyQt::createPtyProcess(IPtyProcess::AutoPty)); + if (!m_ptyProcess) { + const ProcessResultData result = {-1, + QProcess::CrashExit, + QProcess::FailedToStart, + "Failed to create pty process"}; + emit done(result); + return; + } + + bool startResult + = m_ptyProcess->startProcess(program, + arguments, + m_setup.m_workingDirectory.path(), + m_setup.m_environment.toProcessEnvironment().toStringList(), + m_setup.m_ptyData.size().width(), + m_setup.m_ptyData.size().height()); + + if (!startResult) { + const ProcessResultData result = {-1, + QProcess::CrashExit, + QProcess::FailedToStart, + "Failed to start pty process: " + + m_ptyProcess->lastError()}; + emit done(result); + return; + } + + if (!m_ptyProcess->lastError().isEmpty()) { + const ProcessResultData result + = {-1, QProcess::CrashExit, QProcess::FailedToStart, m_ptyProcess->lastError()}; + emit done(result); + return; + } + + connect(m_ptyProcess->notifier(), &QIODevice::readyRead, this, [this] { + emit readyRead(m_ptyProcess->readAll(), {}); + }); + + connect(m_ptyProcess->notifier(), &QIODevice::aboutToClose, this, [this] { + if (m_ptyProcess) { + const ProcessResultData result + = {m_ptyProcess->exitCode(), QProcess::NormalExit, QProcess::UnknownError, {}}; + emit done(result); + return; + } + + const ProcessResultData result = {0, QProcess::NormalExit, QProcess::UnknownError, {}}; + emit done(result); + }); + + emit started(m_ptyProcess->pid()); + } + +private: + std::unique_ptr m_ptyProcess; +}; + class QProcessImpl final : public DefaultImpl { public: @@ -629,6 +725,8 @@ public: ProcessInterface *createProcessInterface() { + if (m_setup.m_terminalMode == TerminalMode::Pty) + return new PtyProcessImpl(); if (m_setup.m_terminalMode != TerminalMode::Off) return Terminal::Hooks::instance().createTerminalProcessInterfaceHook()(); diff --git a/src/plugins/docker/dockerdevice.cpp b/src/plugins/docker/dockerdevice.cpp index 9c6bf8c9bd8..26569166a7d 100644 --- a/src/plugins/docker/dockerdevice.cpp +++ b/src/plugins/docker/dockerdevice.cpp @@ -241,7 +241,7 @@ DockerProcessImpl::DockerProcessImpl(IDevice::ConstPtr device, DockerDevicePriva if (!m_hasReceivedFirstOutput) { QByteArray output = m_process.readAllRawStandardOutput(); qsizetype idx = output.indexOf('\n'); - QByteArray firstLine = output.left(idx); + QByteArray firstLine = output.left(idx).trimmed(); QByteArray rest = output.mid(idx + 1); qCDebug(dockerDeviceLog) << "Process first line received:" << m_process.commandLine() << firstLine; @@ -301,7 +301,9 @@ void DockerProcessImpl::start() = m_devicePrivate->withDockerExecCmd(m_setup.m_commandLine, m_setup.m_environment, m_setup.m_workingDirectory, - interactive); + interactive, + true, + m_setup.m_terminalMode == TerminalMode::Pty); m_process.setCommand(fullCommandLine); m_process.start(); diff --git a/src/plugins/remotelinux/linuxdevice.cpp b/src/plugins/remotelinux/linuxdevice.cpp index 6d611bf7b83..754a7f0281e 100644 --- a/src/plugins/remotelinux/linuxdevice.cpp +++ b/src/plugins/remotelinux/linuxdevice.cpp @@ -656,9 +656,15 @@ void LinuxProcessInterface::handleReadyReadStandardOutput(const QByteArray &outp m_output.append(outputData); static const QByteArray endMarker = s_pidMarker + '\n'; - const int endMarkerOffset = m_output.indexOf(endMarker); - if (endMarkerOffset == -1) - return; + int endMarkerLength = endMarker.length(); + int endMarkerOffset = m_output.indexOf(endMarker); + if (endMarkerOffset == -1) { + static const QByteArray endMarker = s_pidMarker + "\r\n"; + endMarkerOffset = m_output.indexOf(endMarker); + endMarkerLength = endMarker.length(); + if (endMarkerOffset == -1) + return; + } const int startMarkerOffset = m_output.indexOf(s_pidMarker); if (startMarkerOffset == endMarkerOffset) // Only theoretically possible. return; @@ -668,7 +674,7 @@ void LinuxProcessInterface::handleReadyReadStandardOutput(const QByteArray &outp const qint64 processId = pidString.toLongLong(); // We don't want to show output from e.g. /etc/profile. - m_output = m_output.mid(endMarkerOffset + endMarker.length()); + m_output = m_output.mid(endMarkerOffset + endMarkerLength); emitStarted(processId); diff --git a/src/plugins/terminal/CMakeLists.txt b/src/plugins/terminal/CMakeLists.txt index a970b467e19..fafe3e7346e 100644 --- a/src/plugins/terminal/CMakeLists.txt +++ b/src/plugins/terminal/CMakeLists.txt @@ -1,7 +1,7 @@ add_qtc_plugin(Terminal PLUGIN_DEPENDS Core - DEPENDS libvterm ptyqt + DEPENDS libvterm SOURCES celllayout.cpp celllayout.h terminal.qrc diff --git a/src/plugins/terminal/terminalwidget.cpp b/src/plugins/terminal/terminalwidget.cpp index 94cff232d16..572bc74f9e0 100644 --- a/src/plugins/terminal/terminalwidget.cpp +++ b/src/plugins/terminal/terminalwidget.cpp @@ -15,11 +15,11 @@ #include #include -#include #include #include #include +#include #include #include #include @@ -69,7 +69,7 @@ TerminalWidget::TerminalWidget(QWidget *parent, const OpenTerminalParameters &op m_readDelayTimer.setSingleShot(true); m_readDelayTimer.setInterval(10); - connect(&m_readDelayTimer, &QTimer::timeout, this, [this]() { + connect(&m_readDelayTimer, &QTimer::timeout, this, [this] { m_readDelayRestarts = 0; onReadyRead(); }); @@ -80,7 +80,7 @@ TerminalWidget::TerminalWidget(QWidget *parent, const OpenTerminalParameters &op connect(&m_zoomInAction, &QAction::triggered, this, &TerminalWidget::zoomIn); connect(&m_zoomOutAction, &QAction::triggered, this, &TerminalWidget::zoomOut); - connect(&TerminalSettings::instance(), &AspectContainer::applied, this, [this]() { + connect(&TerminalSettings::instance(), &AspectContainer::applied, this, [this] { m_layoutVersion++; // Setup colors first, as setupFont will redraw the screen. setupColors(); @@ -90,7 +90,7 @@ TerminalWidget::TerminalWidget(QWidget *parent, const OpenTerminalParameters &op void TerminalWidget::setupPty() { - m_ptyProcess.reset(PtyQt::createPtyProcess(IPtyProcess::PtyType::AutoPty)); + m_process = std::make_unique(); Environment env = m_openParameters.environment.value_or(Environment::systemEnvironment()); @@ -99,29 +99,17 @@ void TerminalWidget::setupPty() // For git bash on Windows env.prependOrSetPath(shellCommand.executable().parentDir()); + if (env.hasKey("CLINK_NOAUTORUN")) + env.unset("CLINK_NOAUTORUN"); - QStringList envList = filtered(env.toStringList(), [](const QString &envPair) { - return envPair != "CLINK_NOAUTORUN=1"; - }); + m_process->setProcessMode(ProcessMode::Writer); + m_process->setTerminalMode(TerminalMode::Pty); + m_process->setCommand(shellCommand); + m_process->setWorkingDirectory( + m_openParameters.workingDirectory.value_or(FilePath::fromString(QDir::homePath()))); + m_process->setEnvironment(env); - m_ptyProcess->startProcess(shellCommand.executable().nativePath(), - shellCommand.splitArguments(), - m_openParameters.workingDirectory - .value_or(FilePath::fromString(QDir::homePath())) - .nativePath(), - envList, - m_vtermSize.width(), - m_vtermSize.height()); - - emit started(m_ptyProcess->pid()); - - if (!m_ptyProcess->lastError().isEmpty()) { - qCWarning(terminalLog) << m_ptyProcess->lastError(); - m_ptyProcess.reset(); - return; - } - - connect(m_ptyProcess->notifier(), &QIODevice::readyRead, this, [this]() { + connect(m_process.get(), &QtcProcess::readyReadStandardOutput, this, [this] { if (m_readDelayTimer.isActive()) m_readDelayRestarts++; @@ -131,16 +119,19 @@ void TerminalWidget::setupPty() m_readDelayTimer.start(); }); - connect(m_ptyProcess->notifier(), &QIODevice::aboutToClose, this, [this]() { + connect(m_process.get(), &QtcProcess::done, this, [this] { m_cursor.visible = false; - if (m_ptyProcess) { + if (m_process) { onReadyRead(); - if (m_ptyProcess->exitCode() != 0) { + if (m_process->exitCode() != 0) { QByteArray msg = QString("\r\n\033[31mProcess exited with code: %1") - .arg(m_ptyProcess->exitCode()) + .arg(m_process->exitCode()) .toUtf8(); + if (!m_process->errorString().isEmpty()) + msg += QString(" (%1)").arg(m_process->errorString()).toUtf8(); + vterm_input_write(m_vterm.get(), msg.constData(), msg.size()); vterm_screen_flush_damage(m_vtermScreen); @@ -149,10 +140,8 @@ void TerminalWidget::setupPty() } if (m_openParameters.m_exitBehavior == ExitBehavior::Restart) { - QMetaObject::invokeMethod( - this, - [this]() { - m_ptyProcess.reset(); + QMetaObject::invokeMethod(this, [this] { + m_process.reset(); setupPty(); }, Qt::QueuedConnection); @@ -163,13 +152,20 @@ void TerminalWidget::setupPty() if (m_openParameters.m_exitBehavior == ExitBehavior::Keep) { QByteArray msg = QString("\r\nProcess exited with code: %1") - .arg(m_ptyProcess ? m_ptyProcess->exitCode() : -1) + .arg(m_process ? m_process->exitCode() : -1) .toUtf8(); vterm_input_write(m_vterm.get(), msg.constData(), msg.size()); vterm_screen_flush_damage(m_vtermScreen); } }); + + connect(m_process.get(), &QtcProcess::started, this, [this] { + applySizeChange(); + emit started(m_process->processId()); + }); + + m_process->start(); } void TerminalWidget::setupFont() @@ -229,8 +225,8 @@ void TerminalWidget::setupColors() void TerminalWidget::writeToPty(const QByteArray &data) { - if (m_ptyProcess) - m_ptyProcess->write(data); + if (m_process) + m_process->writeRaw(data); } void TerminalWidget::setupVTerm() @@ -305,7 +301,7 @@ void TerminalWidget::setFont(const QFont &font) QAbstractScrollArea::setFont(m_font); - if (m_ptyProcess) { + if (m_process) { applySizeChange(); } } @@ -425,7 +421,7 @@ void TerminalWidget::clearContents() void TerminalWidget::onReadyRead() { - QByteArray data = m_ptyProcess->readAll(); + QByteArray data = m_process->readAllRawStandardOutput(); vterm_input_write(m_vterm.get(), data.constData(), data.size()); vterm_screen_flush_damage(m_vtermScreen); } @@ -709,8 +705,8 @@ void TerminalWidget::applySizeChange() if (m_vtermSize.width() <= 0) m_vtermSize.setWidth(1); - if (m_ptyProcess) - m_ptyProcess->resize(m_vtermSize.width(), m_vtermSize.height()); + if (m_process) + m_process->ptyData().resize(m_vtermSize); vterm_set_size(m_vterm.get(), m_vtermSize.height(), m_vtermSize.width()); vterm_screen_flush_damage(m_vtermScreen); @@ -869,7 +865,7 @@ void TerminalWidget::showEvent(QShowEvent *event) { Q_UNUSED(event); - if (!m_ptyProcess) + if (!m_process) setupPty(); QAbstractScrollArea::showEvent(event); diff --git a/src/plugins/terminal/terminalwidget.h b/src/plugins/terminal/terminalwidget.h index 3f1590dfaaf..96e24f41172 100644 --- a/src/plugins/terminal/terminalwidget.h +++ b/src/plugins/terminal/terminalwidget.h @@ -5,6 +5,7 @@ #include "scrollback.h" +#include #include #include @@ -12,7 +13,6 @@ #include #include -#include #include #include @@ -111,7 +111,7 @@ protected: void updateScrollBars(); private: - std::unique_ptr m_ptyProcess; + std::unique_ptr m_process; std::unique_ptr m_vterm; VTermScreen *m_vtermScreen;