Files
orbithub/third_party/KodoTerm/src/PtyProcess_unix.cpp
T

184 lines
4.6 KiB
C++

// SPDX-License-Identifier: MIT
// Author: Diego Iastrubni <diegoiast@gmail.com>
#include "PtyProcess_unix.h"
#include <QCoreApplication>
#include <QDebug>
#if defined(__APPLE__) || defined(__FreeBSD__)
#include <util.h>
#else
#include <pty.h>
#endif
#include <QFile>
#include <QFileInfo>
#include <cerrno>
#include <fcntl.h>
#include <signal.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <termios.h>
#include <unistd.h>
PtyProcessUnix::PtyProcessUnix(QObject *parent) : PtyProcess(parent) {}
PtyProcessUnix::~PtyProcessUnix() {
kill();
if (m_masterFd >= 0) {
::close(m_masterFd);
m_masterFd = -1;
}
}
bool PtyProcessUnix::start(const QSize &size) {
if (m_program.isEmpty()) {
return false;
}
struct winsize ws;
ws.ws_row = (unsigned short)size.height();
ws.ws_col = (unsigned short)size.width();
ws.ws_xpixel = 0;
ws.ws_ypixel = 0;
pid_t pid = forkpty(&m_masterFd, nullptr, nullptr, &ws);
if (pid == -1) {
qWarning() << "Failed to forkpty";
return false;
}
if (pid == 0) {
// Child
// Working directory
if (!m_workingDirectory.isEmpty()) {
if (chdir(m_workingDirectory.toLocal8Bit().constData()) != 0) {
_exit(1);
}
}
// Environment
for (const auto &key : m_environment.keys()) {
setenv(key.toLocal8Bit().constData(),
m_environment.value(key).toLocal8Bit().constData(), 1);
}
setenv("TERM", "xterm-256color", 1);
// Convert args to char* array
std::vector<char *> args;
QByteArray progBytes = m_program.toLocal8Bit();
args.push_back(progBytes.data());
// Helper to keep storage alive
std::vector<QByteArray> storage;
storage.reserve(m_arguments.size());
for (const auto &arg : m_arguments) {
storage.push_back(arg.toLocal8Bit());
args.push_back(storage.back().data());
}
args.push_back(nullptr);
execvp(m_program.toLocal8Bit().constData(), args.data());
// If execvp returns, it failed
_exit(1);
} else {
// Parent
m_pid = pid;
m_notifier = new QSocketNotifier(m_masterFd, QSocketNotifier::Read, this);
connect(m_notifier, &QSocketNotifier::activated, this, &PtyProcessUnix::onReadyRead);
return true;
}
}
bool PtyProcessUnix::start(const QString &program, const QStringList &arguments,
const QSize &size) {
return PtyProcess::start(program, arguments, size);
}
void PtyProcessUnix::write(const QByteArray &data) {
if (m_masterFd >= 0) {
::write(m_masterFd, data.constData(), data.size());
}
}
void PtyProcessUnix::resize(const QSize &size) {
if (m_masterFd >= 0) {
struct winsize ws;
ws.ws_row = (unsigned short)size.height();
ws.ws_col = (unsigned short)size.width();
ws.ws_xpixel = 0;
ws.ws_ypixel = 0;
ioctl(m_masterFd, TIOCSWINSZ, &ws);
}
}
void PtyProcessUnix::kill() {
if (m_pid > 0) {
::kill(m_pid, SIGTERM);
// Wait? usually waitpid via signal handler, but for now just cleanup
m_pid = -1;
}
}
bool PtyProcessUnix::isRoot() const {
if (m_masterFd < 0) {
return false;
}
pid_t pgrp = tcgetpgrp(m_masterFd);
if (pgrp <= 0) {
// Fallback to initial pid
if (m_pid <= 0) {
return false;
}
pgrp = m_pid;
}
struct stat st;
char path[64];
snprintf(path, sizeof(path), "/proc/%d", (int)pgrp);
if (stat(path, &st) == 0) {
return st.st_uid == 0;
}
return false;
}
QString PtyProcessUnix::foregroundProcessName() const {
if (m_masterFd < 0) {
return QString();
}
pid_t pgrp = tcgetpgrp(m_masterFd);
if (pgrp <= 0) {
return QFileInfo(m_program).baseName();
}
char path[64];
snprintf(path, sizeof(path), "/proc/%d/comm", (int)pgrp);
QFile file(path);
if (file.open(QIODevice::ReadOnly)) {
return QString::fromUtf8(file.readAll().trimmed());
}
// Fallback to initial program
return QFileInfo(m_program).baseName();
}
void PtyProcessUnix::onReadyRead() {
char buffer[4096];
ssize_t len = ::read(m_masterFd, buffer, sizeof(buffer));
if (len > 0) {
emit readyRead(QByteArray(buffer, (int)len));
} else if (len < 0 && errno != EAGAIN) {
m_notifier->setEnabled(false);
emit finished(-1, -1); // Error
} else if (len == 0) {
m_notifier->setEnabled(false);
emit finished(0, 0); // EOF
}
}