forked from qt-creator/qt-creator
Utils: Integrate ptyqt into qtcprocess
Integrating PtyQt directly into QtcProcess allows us to start Pseudo terminal processes using the existing QtcProcess functionality such as starting remote process on e.g. docker or remote linux devices. This is needed for the new Terminal plugin. Change-Id: Iaeed5ff9b341ba4646d955b2ed9577a18cd7100f Reviewed-by: Jarek Kobus <jaroslaw.kobus@qt.io> Reviewed-by: Cristian Adam <cristian.adam@qt.io>
This commit is contained in:
16
src/libs/3rdparty/libptyqt/conptyprocess.cpp
vendored
16
src/libs/3rdparty/libptyqt/conptyprocess.cpp
vendored
@@ -123,6 +123,7 @@ bool ConPtyProcess::startProcess(const QString &executable,
|
||||
}
|
||||
|
||||
m_shellPath = executable;
|
||||
m_shellPath.replace('/', '\\');
|
||||
m_size = QPair<qint16, qint16>(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;
|
||||
|
@@ -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();
|
||||
|
||||
|
1
src/libs/3rdparty/libptyqt/winptyprocess.cpp
vendored
1
src/libs/3rdparty/libptyqt/winptyprocess.cpp
vendored
@@ -60,6 +60,7 @@ bool WinPtyProcess::startProcess(const QString &executable,
|
||||
}
|
||||
|
||||
m_shellPath = executable;
|
||||
m_shellPath.replace('/', '\\');
|
||||
m_size = QPair<qint16, qint16>(cols, rows);
|
||||
|
||||
#ifdef PTYQT_DEBUG
|
||||
|
1
src/libs/3rdparty/winpty/src/CMakeLists.txt
vendored
1
src/libs/3rdparty/winpty/src/CMakeLists.txt
vendored
@@ -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
|
||||
|
@@ -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
|
||||
|
@@ -24,6 +24,7 @@ enum class ProcessImpl {
|
||||
|
||||
enum class TerminalMode {
|
||||
Off,
|
||||
Pty,
|
||||
Run,
|
||||
Debug,
|
||||
Suspend,
|
||||
|
@@ -16,6 +16,9 @@
|
||||
#include "threadutils.h"
|
||||
#include "utilstr.h"
|
||||
|
||||
#include <iptyprocess.h>
|
||||
#include <ptyqt.h>
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QDebug>
|
||||
#include <QDir>
|
||||
@@ -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<IPtyProcess> 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()();
|
||||
|
||||
|
@@ -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();
|
||||
|
@@ -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);
|
||||
|
||||
|
@@ -1,7 +1,7 @@
|
||||
|
||||
add_qtc_plugin(Terminal
|
||||
PLUGIN_DEPENDS Core
|
||||
DEPENDS libvterm ptyqt
|
||||
DEPENDS libvterm
|
||||
SOURCES
|
||||
celllayout.cpp celllayout.h
|
||||
terminal.qrc
|
||||
|
@@ -15,11 +15,11 @@
|
||||
#include <utils/processinterface.h>
|
||||
#include <utils/stringutils.h>
|
||||
|
||||
#include <ptyqt.h>
|
||||
#include <vterm.h>
|
||||
|
||||
#include <QApplication>
|
||||
#include <QClipboard>
|
||||
#include <QElapsedTimer>
|
||||
#include <QGlyphRun>
|
||||
#include <QLoggingCategory>
|
||||
#include <QPaintEvent>
|
||||
@@ -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<QtcProcess>();
|
||||
|
||||
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);
|
||||
|
@@ -5,6 +5,7 @@
|
||||
|
||||
#include "scrollback.h"
|
||||
|
||||
#include <utils/qtcprocess.h>
|
||||
#include <utils/terminalhooks.h>
|
||||
|
||||
#include <QAbstractScrollArea>
|
||||
@@ -12,7 +13,6 @@
|
||||
#include <QTextLayout>
|
||||
#include <QTimer>
|
||||
|
||||
#include <iptyprocess.h>
|
||||
#include <vterm.h>
|
||||
|
||||
#include <memory>
|
||||
@@ -111,7 +111,7 @@ protected:
|
||||
void updateScrollBars();
|
||||
|
||||
private:
|
||||
std::unique_ptr<IPtyProcess> m_ptyProcess;
|
||||
std::unique_ptr<Utils::QtcProcess> m_process;
|
||||
|
||||
std::unique_ptr<VTerm, void (*)(VTerm *)> m_vterm;
|
||||
VTermScreen *m_vtermScreen;
|
||||
|
Reference in New Issue
Block a user