Chris@57: /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ Chris@57: Chris@57: /* Chris@57: EasyMercurial Chris@57: Chris@57: Based on HgExplorer by Jari Korhonen Chris@57: Copyright (c) 2010 Jari Korhonen Chris@644: Copyright (c) 2013 Chris Cannam Chris@644: Copyright (c) 2013 Queen Mary, University of London Chris@57: Chris@57: This program is free software; you can redistribute it and/or Chris@57: modify it under the terms of the GNU General Public License as Chris@57: published by the Free Software Foundation; either version 2 of the Chris@57: License, or (at your option) any later version. See the file Chris@57: COPYING included with this distribution for more information. Chris@57: */ jtkorhonen@0: jtkorhonen@0: #include "hgrunner.h" Chris@62: #include "common.h" Chris@57: #include "debug.h" Chris@240: #include "settingsdialog.h" Chris@50: Chris@62: #include Chris@75: #include Chris@663: #include Chris@188: #include Chris@161: #include Chris@561: #include Chris@571: #include Chris@561: #include Chris@699: #include jtkorhonen@0: Chris@43: #include Chris@75: #include Chris@75: #include Chris@111: #include jtkorhonen@0: Chris@76: #ifndef Q_OS_WIN32 Chris@111: #include Chris@113: #include Chris@111: #include chris@479: #else chris@479: #include Chris@80: #endif Chris@76: Chris@561: HgRunner::HgRunner(QString myDirPath, QWidget *parent) : Chris@561: QWidget(parent), Chris@699: m_ptyFile(0), Chris@699: m_proc(0), Chris@699: m_myDirPath(myDirPath) jtkorhonen@0: { Chris@561: QGridLayout *layout = new QGridLayout(this); Chris@561: layout->setMargin(0); Chris@561: Chris@561: m_progress = new QProgressBar; Chris@561: layout->addWidget(m_progress, 0, 0); Chris@561: Chris@571: m_cancel = new QPushButton; Chris@561: m_cancel->setIcon(QIcon(":images/cancel-small.png")); Chris@571: m_cancel->setFlat(true); Chris@571: m_cancel->setFixedHeight(m_progress->sizeHint().height()); Chris@571: m_cancel->setFixedWidth(m_progress->sizeHint().height()); Chris@561: connect(m_cancel, SIGNAL(clicked()), this, SLOT(killCurrentActions())); Chris@561: layout->addWidget(m_cancel, 0, 1); Chris@561: Chris@113: m_proc = 0; Chris@84: Chris@239: // Always unbundle the extension: even if it already exists (in Chris@239: // case we're upgrading) and even if we're not going to use it (so Chris@239: // that it's available in case someone wants to use it later, Chris@239: // e.g. to fix a malfunctioning setup). But the path we actually Chris@239: // prefer is the one in the settings first, if it exists; then the Chris@239: // unbundled one; then anything in the path if for some reason Chris@239: // unbundling failed Chris@239: unbundleExtension(); Chris@239: Chris@561: m_progress->setTextVisible(false); Chris@561: hide(); Chris@84: m_isRunning = false; jtkorhonen@0: } jtkorhonen@0: jtkorhonen@0: HgRunner::~HgRunner() jtkorhonen@0: { Chris@111: closeTerminal(); Chris@120: if (m_proc) { Chris@120: m_proc->kill(); Chris@122: m_proc->deleteLater(); Chris@120: } Chris@444: if (m_authFilePath != "") { Chris@444: QFile(m_authFilePath).remove(); Chris@444: } Chris@444: //!!! and remove any other misc auth file paths... jtkorhonen@0: } jtkorhonen@0: Chris@188: QString HgRunner::getUnbundledFileName() Chris@188: { Chris@240: return SettingsDialog::getUnbundledExtensionFileName(); Chris@188: } Chris@188: Chris@180: QString HgRunner::unbundleExtension() Chris@161: { Chris@188: // Pull out the bundled Python file into a temporary file, and Chris@188: // copy it to our known extension location, replacing the magic Chris@188: // text NO_EASYHG_IMPORT_PATH with our installation location Chris@188: Chris@161: QString bundled = ":easyhg.py"; Chris@188: QString unbundled = getUnbundledFileName(); Chris@188: Chris@188: QString target = QFileInfo(unbundled).path(); Chris@161: if (!QDir().mkpath(target)) { Chris@161: DEBUG << "Failed to make unbundle path " << target << endl; Chris@188: std::cerr << "Failed to make unbundle path " << target << std::endl; Chris@180: return ""; Chris@161: } Chris@188: Chris@161: QFile bf(bundled); Chris@188: DEBUG << "unbundle: bundled file will be " << bundled << endl; Chris@188: if (!bf.exists() || !bf.open(QIODevice::ReadOnly)) { Chris@180: DEBUG << "Bundled extension is missing!" << endl; Chris@180: return ""; Chris@180: } Chris@188: Chris@188: QTemporaryFile tmpfile(QString("%1/easyhg.py.XXXXXX").arg(target)); Chris@188: tmpfile.setAutoRemove(false); Chris@188: DEBUG << "unbundle: temp file will be " << tmpfile.fileName() << endl; Chris@188: if (!tmpfile.open()) { Chris@188: DEBUG << "Failed to open temporary file " << tmpfile.fileName() << endl; Chris@188: std::cerr << "Failed to open temporary file " << tmpfile.fileName() << std::endl; Chris@180: return ""; Chris@161: } Chris@188: Chris@188: QString all = QString::fromUtf8(bf.readAll()); Chris@188: all.replace("NO_EASYHG_IMPORT_PATH", m_myDirPath); Chris@188: tmpfile.write(all.toUtf8()); Chris@188: DEBUG << "unbundle: wrote " << all.length() << " characters" << endl; Chris@188: Chris@188: tmpfile.close(); Chris@188: Chris@188: QFile ef(unbundled); Chris@188: if (ef.exists()) { Chris@188: DEBUG << "unbundle: removing old file " << unbundled << endl; Chris@188: ef.remove(); Chris@188: } Chris@188: DEBUG << "unbundle: renaming " << tmpfile.fileName() << " to " << unbundled << endl; Chris@188: if (!tmpfile.rename(unbundled)) { Chris@188: DEBUG << "Failed to move temporary file to target file " << unbundled << endl; Chris@188: std::cerr << "Failed to move temporary file to target file " << unbundled << std::endl; Chris@188: return ""; Chris@188: } Chris@188: Chris@188: DEBUG << "Unbundled extension to " << unbundled << endl; Chris@188: return unbundled; Chris@161: } Chris@161: Chris@109: void HgRunner::requestAction(HgAction action) Chris@109: { Chris@605: DEBUG << "requestAction " << action.action << ": " << m_queue.size() << " thing(s) in queue, current action is " << m_currentAction.action << endl; Chris@109: bool pushIt = true; Chris@605: Chris@605: action = expandEnvironment(action); Chris@605: Chris@109: if (m_queue.empty()) { Chris@109: if (action == m_currentAction) { Chris@109: // this request is identical to the thing we're executing Chris@109: DEBUG << "requestAction: we're already handling this one, ignoring identical request" << endl; Chris@109: pushIt = false; Chris@109: } Chris@109: } else { Chris@109: HgAction last = m_queue.back(); Chris@109: if (action == last) { Chris@109: // this request is identical to the previous thing we Chris@109: // queued which we haven't executed yet Chris@109: DEBUG << "requestAction: we're already queueing this one, ignoring identical request" << endl; Chris@109: pushIt = false; Chris@109: } Chris@109: } Chris@605: if (pushIt) { Chris@605: m_queue.push_back(action); Chris@605: } Chris@109: checkQueue(); Chris@109: } Chris@109: Chris@605: HgAction HgRunner::expandEnvironment(HgAction action) Chris@605: { Chris@605: // Adjust the executable and params for action to match our actual Chris@605: // environment. We do this when the action is received, rather Chris@605: // than when we execute it, so that we can compare Chris@605: // (post-expansion) commands to see e.g. whether the one just Chris@605: // received is the same as the one we're currently executing Chris@605: Chris@605: QString executable = action.executable; Chris@605: QStringList params = action.params; Chris@605: Chris@605: if (executable == "") { Chris@605: // This is a Hg command Chris@605: executable = getHgBinaryName(); Chris@605: if (executable == "") executable = "hg"; Chris@605: Chris@605: QString ssh = getSshBinaryName(); Chris@605: if (ssh != "") { Chris@605: params.push_front(QString("ui.ssh=\"%1\"").arg(ssh)); Chris@605: params.push_front("--config"); Chris@605: } Chris@605: Chris@605: if (action.mayBeInteractive()) { Chris@605: params.push_front("ui.interactive=true"); Chris@605: params.push_front("--config"); Chris@605: QSettings settings; Chris@605: if (settings.value("useextension", true).toBool()) { Chris@605: params = addExtensionOptions(params); Chris@605: } Chris@605: } Chris@605: } Chris@605: Chris@605: action.executable = executable; Chris@605: action.params = params; Chris@605: Chris@605: return action; Chris@605: } Chris@605: Chris@239: QString HgRunner::getHgBinaryName() Chris@62: { Chris@62: QSettings settings; Chris@175: settings.beginGroup("Locations"); Chris@239: return settings.value("hgbinary", "").toString(); Chris@62: } Chris@62: chris@406: QString HgRunner::getSshBinaryName() chris@406: { chris@406: QSettings settings; chris@406: settings.beginGroup("Locations"); chris@406: return settings.value("sshbinary", "").toString(); chris@406: } chris@406: Chris@239: QString HgRunner::getExtensionLocation() Chris@239: { Chris@239: QSettings settings; Chris@239: settings.beginGroup("Locations"); Chris@239: QString extpath = settings.value("extensionpath", "").toString(); Chris@239: if (extpath != "" && QFile(extpath).exists()) return extpath; Chris@239: return ""; Chris@239: } Chris@239: jtkorhonen@0: void HgRunner::started() jtkorhonen@0: { Chris@104: DEBUG << "started" << endl; Chris@75: /* Chris@104: m_proc->write("blah\n"); Chris@104: m_proc->write("blah\n"); Chris@104: m_proc -> closeWriteChannel(); Chris@75: */ jtkorhonen@0: } jtkorhonen@0: Chris@84: void HgRunner::noteUsername(QString name) Chris@75: { Chris@84: m_userName = name; Chris@75: } Chris@75: Chris@84: void HgRunner::noteRealm(QString realm) Chris@75: { Chris@84: m_realm = realm; Chris@84: } Chris@84: Chris@84: void HgRunner::getUsername() Chris@84: { Chris@113: if (m_ptyFile) { Chris@84: bool ok = false; Chris@84: QString prompt = tr("User name:"); Chris@84: if (m_realm != "") { Chris@84: prompt = tr("User name for \"%1\":").arg(m_realm); Chris@84: } Chris@691: QString name = QInputDialog::getText Chris@84: (qobject_cast(parent()), Chris@84: tr("Enter user name"), prompt, Chris@84: QLineEdit::Normal, QString(), &ok); Chris@84: if (ok) { Chris@691: m_ptyFile->write(QString("%1\n").arg(name).toUtf8()); Chris@113: m_ptyFile->flush(); Chris@84: return; Chris@111: } else { Chris@111: DEBUG << "HgRunner::getUsername: user cancelled" << endl; Chris@111: killCurrentCommand(); Chris@111: return; Chris@84: } Chris@691: } else { // usual on win32 Chris@691: DEBUG << "HgRunner::getUsername: can't handle without pty" << endl; Chris@691: emit commandFailed(m_currentAction, "", "Host requires authentication, but we can't handle that without the EasyHg extension loaded"); Chris@84: } Chris@84: // user cancelled or something went wrong Chris@111: DEBUG << "HgRunner::getUsername: something went wrong" << endl; Chris@84: killCurrentCommand(); Chris@84: } Chris@84: Chris@84: void HgRunner::getPassword() Chris@84: { Chris@113: if (m_ptyFile) { Chris@84: bool ok = false; Chris@84: QString prompt = tr("Password:"); Chris@84: if (m_userName != "") { Chris@84: if (m_realm != "") { Chris@84: prompt = tr("Password for \"%1\" at \"%2\":") Chris@84: .arg(m_userName).arg(m_realm); Chris@75: } else { Chris@84: prompt = tr("Password for user \"%1\":") Chris@84: .arg(m_userName); Chris@75: } Chris@75: } Chris@84: QString pwd = QInputDialog::getText Chris@84: (qobject_cast(parent()), Chris@691: tr("Enter password"), prompt, Chris@84: QLineEdit::Password, QString(), &ok); Chris@84: if (ok) { Chris@113: m_ptyFile->write(QString("%1\n").arg(pwd).toUtf8()); Chris@113: m_ptyFile->flush(); Chris@84: return; Chris@111: } else { Chris@111: DEBUG << "HgRunner::getPassword: user cancelled" << endl; Chris@111: killCurrentCommand(); Chris@111: return; Chris@84: } Chris@691: } else { // usual on win32 Chris@691: DEBUG << "HgRunner::getPassword: can't handle without pty" << endl; Chris@691: emit commandFailed(m_currentAction, "", "Host requires authentication, but we can't handle that without the EasyHg extension loaded"); Chris@75: } Chris@84: // user cancelled or something went wrong Chris@111: DEBUG << "HgRunner::getPassword: something went wrong" << endl; Chris@84: killCurrentCommand(); Chris@84: } Chris@84: Chris@113: bool HgRunner::checkPrompts(QString chunk) Chris@84: { Chris@93: //DEBUG << "checkPrompts: " << chunk << endl; Chris@84: Chris@128: if (!m_currentAction.mayBeInteractive()) return false; Chris@128: Chris@84: QString text = chunk.trimmed(); Chris@84: QString lower = text.toLower(); Chris@84: if (lower.endsWith("password:")) { Chris@84: getPassword(); Chris@113: return true; Chris@84: } Chris@128: if (lower.endsWith("user:") || lower.endsWith("username:")) { Chris@84: getUsername(); Chris@113: return true; Chris@84: } Chris@128: QRegExp userRe("\\buser(name)?:\\s*([^\\s]+)"); Chris@84: if (userRe.indexIn(text) >= 0) { Chris@128: noteUsername(userRe.cap(2)); Chris@84: } Chris@84: QRegExp realmRe("\\brealmr:\\s*([^\\s]+)"); Chris@84: if (realmRe.indexIn(text) >= 0) { Chris@84: noteRealm(realmRe.cap(1)); Chris@84: } Chris@113: return false; Chris@84: } Chris@84: Chris@110: void HgRunner::dataReadyStdout() Chris@84: { Chris@734: // DEBUG << "dataReadyStdout" << endl; Chris@408: if (!m_proc) return; Chris@110: QString chunk = QString::fromUtf8(m_proc->readAllStandardOutput()); Chris@113: if (!checkPrompts(chunk)) { Chris@113: m_stdout += chunk; Chris@113: } Chris@110: } Chris@110: Chris@110: void HgRunner::dataReadyStderr() Chris@110: { Chris@734: // DEBUG << "dataReadyStderr" << endl; Chris@408: if (!m_proc) return; Chris@110: QString chunk = QString::fromUtf8(m_proc->readAllStandardError()); Chris@734: // DEBUG << chunk; Chris@113: if (!checkPrompts(chunk)) { Chris@113: m_stderr += chunk; Chris@113: } Chris@113: } Chris@113: Chris@113: void HgRunner::dataReadyPty() Chris@113: { Chris@734: // DEBUG << "dataReadyPty" << endl; Chris@113: QString chunk = QString::fromUtf8(m_ptyFile->readAll()); Chris@734: // DEBUG << "chunk of " << chunk.length() << " chars" << endl; Chris@113: if (!checkPrompts(chunk)) { Chris@113: m_stdout += chunk; Chris@113: } Chris@75: } Chris@75: Chris@189: void HgRunner::error(QProcess::ProcessError) Chris@189: { Chris@189: finished(-1, QProcess::CrashExit); Chris@189: } Chris@189: jtkorhonen@0: void HgRunner::finished(int procExitCode, QProcess::ExitStatus procExitStatus) jtkorhonen@0: { Chris@564: if (!m_proc) return; chris@385: Chris@564: // Save the current action and reset m_currentAction before we Chris@109: // emit a signal to mark the completion; otherwise we may be Chris@109: // resetting the action after a slot has already tried to set it Chris@109: // to something else to start a new action Chris@109: Chris@109: HgAction completedAction = m_currentAction; Chris@109: Chris@605: DEBUG << "HgRunner::finished: completed " << completedAction.action << endl; Chris@605: Chris@84: m_isRunning = false; Chris@109: m_currentAction = HgAction(); Chris@84: Chris@195: //closeProcInput(); Chris@189: m_proc->deleteLater(); Chris@113: m_proc = 0; jtkorhonen@0: Chris@109: if (completedAction.action == ACT_NONE) { Chris@109: DEBUG << "HgRunner::finished: WARNING: completed action is ACT_NONE" << endl; Chris@62: } else { Chris@113: if (procExitCode == 0 && procExitStatus == QProcess::NormalExit) { Chris@691: DEBUG << "HgRunner::finished: Command completed successfully" << endl; Chris@124: // DEBUG << "stdout is " << m_stdout << endl; Chris@691: emit commandCompleted(completedAction, m_stdout, m_stderr); Chris@113: } else { Chris@113: DEBUG << "HgRunner::finished: Command failed, exit code " Chris@666: << procExitCode << ", exit status " << int(procExitStatus) Chris@113: << ", stderr follows" << endl; Chris@113: DEBUG << m_stderr << endl; Chris@691: emit commandFailed(completedAction, m_stdout, m_stderr); Chris@113: } jtkorhonen@0: } Chris@109: Chris@109: checkQueue(); jtkorhonen@0: } jtkorhonen@0: Chris@182: void HgRunner::killCurrentActions() Chris@182: { Chris@564: HgAction current = m_currentAction; Chris@182: m_queue.clear(); Chris@182: killCurrentCommand(); Chris@564: emit commandCancelled(current); Chris@182: } Chris@182: Chris@62: void HgRunner::killCurrentCommand() jtkorhonen@0: { Chris@109: if (m_isRunning) { Chris@113: m_currentAction.action = ACT_NONE; // so that we don't bother to notify chris@385: if (m_proc) m_proc->kill(); jtkorhonen@0: } jtkorhonen@0: } jtkorhonen@0: Chris@109: void HgRunner::checkQueue() Chris@62: { Chris@109: if (m_isRunning) { Chris@109: return; Chris@109: } Chris@109: if (m_queue.empty()) { Chris@109: hide(); Chris@109: return; Chris@109: } Chris@109: HgAction toRun = m_queue.front(); Chris@109: m_queue.pop_front(); Chris@109: DEBUG << "checkQueue: have action: running " << toRun.action << endl; Chris@109: startCommand(toRun); Chris@109: } Chris@109: Chris@455: void HgRunner::pruneOldAuthFiles() Chris@455: { Chris@663: QString path = QStandardPaths::writableLocation Chris@663: (QStandardPaths::CacheLocation); Chris@455: QDir d(path); Chris@455: if (!d.exists()) return; Chris@455: QStringList filters; Chris@455: filters << "easyhg.*.dat"; Chris@455: QStringList fl = d.entryList(filters); Chris@455: foreach (QString f, fl) { Chris@455: QStringList parts = f.split('.'); Chris@455: if (parts.size() > 1) { Chris@455: int pid = parts[1].toInt(); Chris@455: DEBUG << "Checking pid " << pid << " for cache file " << f << endl; Chris@455: Chris@455: ProcessStatus ps = GetProcessStatus(pid); Chris@455: if (ps == ProcessNotRunning) { Chris@455: DEBUG << "Removing stale cache file " << f << endl; Chris@455: QDir(d).remove(f); Chris@455: } Chris@455: } Chris@455: } Chris@455: } Chris@455: Chris@455: QString HgRunner::getAuthFilePath() Chris@455: { Chris@455: if (m_authFilePath == "") { Chris@455: Chris@455: pruneOldAuthFiles(); Chris@455: Chris@455: QByteArray fileExt = randomKey(); Chris@455: if (fileExt == QByteArray()) { Chris@674: DEBUG << "HgRunner::getAuthFilePath: Failed to get proper auth file ext" << endl; Chris@455: return ""; Chris@455: } Chris@455: QString fileExt16 = QString::fromLocal8Bit(fileExt.toBase64()).left(16) Chris@455: .replace('+', '-').replace('/', '_'); Chris@663: QString path = QStandardPaths::writableLocation Chris@663: (QStandardPaths::CacheLocation); Chris@455: QDir().mkpath(path); Chris@455: if (path == "") { Chris@674: DEBUG << "HgRunner::getAuthFilePath: Failed to get cache location" << endl; Chris@455: return ""; Chris@455: } Chris@455: Chris@455: m_authFilePath = QString("%1/easyhg.%2.%3.dat").arg(path) Chris@455: .arg(getpid()).arg(fileExt16); Chris@455: } Chris@455: Chris@455: return m_authFilePath; Chris@455: } Chris@455: Chris@455: QString HgRunner::getAuthKey() Chris@455: { Chris@455: if (m_authKey == "") { Chris@455: QByteArray key = randomKey(); Chris@455: if (key == QByteArray()) { Chris@674: DEBUG << "HgRunner::getAuthKey: Failed to get proper auth key" << endl; Chris@455: return ""; Chris@455: } Chris@455: QString key16 = QString::fromLocal8Bit(key.toBase64()).left(16); Chris@455: m_authKey = key16; Chris@455: } Chris@455: Chris@455: return m_authKey; Chris@455: } Chris@455: Chris@444: QStringList HgRunner::addExtensionOptions(QStringList params) Chris@444: { Chris@444: QString extpath = getExtensionLocation(); Chris@444: if (extpath == "") { Chris@444: DEBUG << "HgRunner::addExtensionOptions: Failed to get extension location" << endl; Chris@444: return params; Chris@444: } Chris@444: Chris@455: QString afp = getAuthFilePath(); Chris@455: QString afk = getAuthKey(); Chris@444: Chris@455: if (afp != "" && afk != "") { Chris@455: params.push_front(QString("easyhg.authkey=%1").arg(m_authKey)); Chris@455: params.push_front("--config"); Chris@455: params.push_front(QString("easyhg.authfile=%1").arg(m_authFilePath)); Chris@455: params.push_front("--config"); Chris@444: } Chris@444: Chris@444: // Looks like this one must be without quotes, even though the SSH Chris@444: // one above only works on Windows if it has quotes (at least where Chris@444: // there is a space in the path). Odd Chris@444: params.push_front(QString("extensions.easyhg=%1").arg(extpath)); Chris@444: params.push_front("--config"); Chris@444: Chris@444: return params; Chris@444: } Chris@444: Chris@109: void HgRunner::startCommand(HgAction action) Chris@109: { Chris@340: if (action.workingDir.isEmpty()) { Chris@340: // We require a working directory, never just operate in pwd Chris@691: emit commandFailed(action, "", "EasyMercurial: No working directory supplied, will not run Mercurial command without one"); Chris@340: return; Chris@340: } Chris@340: Chris@84: m_isRunning = true; Chris@561: m_progress->setRange(0, 0); Chris@571: if (!action.shouldBeFast()) { Chris@571: show(); Chris@571: m_cancel->setVisible(action.makesSenseToCancel()); Chris@571: } Chris@110: m_stdout.clear(); Chris@110: m_stderr.clear(); Chris@84: m_realm = ""; Chris@84: m_userName = ""; jtkorhonen@0: Chris@113: m_proc = new QProcess; Chris@113: Chris@177: QProcessEnvironment env = QProcessEnvironment::systemEnvironment(); Chris@177: Chris@177: #ifdef Q_OS_WIN32 Chris@257: // On Win32 we like to bundle Hg and other executables with EasyHg Chris@177: if (m_myDirPath != "") { Chris@177: env.insert("PATH", m_myDirPath + ";" + env.value("PATH")); Chris@172: } Chris@172: #endif Chris@172: Chris@257: #ifdef Q_OS_MAC Chris@490: if (QSettings().value("python32", false).toBool()) { Chris@261: env.insert("VERSIONER_PYTHON_PREFER_32_BIT", "1"); Chris@257: } Chris@699: QDir pluginDir(QCoreApplication::applicationDirPath()); Chris@699: pluginDir.cd("../plugins"); Chris@699: env.insert("QT_PLUGIN_PATH", pluginDir.canonicalPath()); Chris@257: #endif Chris@257: Chris@113: env.insert("LANG", "en_US.utf8"); Chris@113: env.insert("LC_ALL", "en_US.utf8"); Chris@408: env.insert("HGENCODING", "utf8"); Chris@113: env.insert("HGPLAIN", "1"); Chris@113: m_proc->setProcessEnvironment(env); Chris@113: Chris@113: connect(m_proc, SIGNAL(started()), this, SLOT(started())); Chris@189: connect(m_proc, SIGNAL(error(QProcess::ProcessError)), Chris@189: this, SLOT(error(QProcess::ProcessError))); Chris@113: connect(m_proc, SIGNAL(finished(int, QProcess::ExitStatus)), Chris@113: this, SLOT(finished(int, QProcess::ExitStatus))); Chris@113: connect(m_proc, SIGNAL(readyReadStandardOutput()), Chris@113: this, SLOT(dataReadyStdout())); Chris@113: connect(m_proc, SIGNAL(readyReadStandardError()), Chris@113: this, SLOT(dataReadyStderr())); Chris@113: Chris@340: m_proc->setWorkingDirectory(action.workingDir); jtkorhonen@0: Chris@605: if (action.mayBeInteractive()) { Chris@111: openTerminal(); Chris@111: if (m_ptySlaveFilename != "") { Chris@113: DEBUG << "HgRunner: connecting to pseudoterminal" << endl; Chris@107: m_proc->setStandardInputFile(m_ptySlaveFilename); Chris@114: // m_proc->setStandardOutputFile(m_ptySlaveFilename); Chris@113: // m_proc->setStandardErrorFile(m_ptySlaveFilename); Chris@107: } Chris@84: } Chris@84: Chris@605: QString cmdline = action.executable; Chris@605: foreach (QString param, action.params) cmdline += " " + param; Chris@694: Chris@694: QString reportable = cmdline; Chris@694: reportable.replace(QRegExp("authkey=[^ ]*"), "authkey="); Chris@694: DEBUG << "HgRunner: starting: " << reportable << " with cwd " Chris@109: << action.workingDir << endl; Chris@43: Chris@109: m_currentAction = action; Chris@109: Chris@109: DEBUG << "set current action to " << m_currentAction.action << endl; Chris@109: Chris@238: emit commandStarting(action); Chris@238: Chris@605: m_proc->start(action.executable, action.params); Chris@84: } Chris@84: Chris@84: void HgRunner::closeProcInput() Chris@84: { Chris@84: DEBUG << "closeProcInput" << endl; Chris@84: chris@385: if (m_proc) m_proc->closeWriteChannel(); Chris@111: } Chris@111: Chris@111: void HgRunner::openTerminal() Chris@111: { Chris@111: #ifndef Q_OS_WIN32 Chris@111: if (m_ptySlaveFilename != "") return; // already open Chris@111: DEBUG << "HgRunner::openTerminal: trying to open new pty" << endl; Chris@111: int master = posix_openpt(O_RDWR | O_NOCTTY); Chris@111: if (master < 0) { Chris@111: DEBUG << "openpt failed" << endl; Chris@111: perror("openpt failed"); Chris@111: return; Chris@111: } Chris@113: struct termios t; Chris@113: if (tcgetattr(master, &t)) { Chris@113: DEBUG << "tcgetattr failed" << endl; Chris@113: perror("tcgetattr failed"); Chris@113: } Chris@113: cfmakeraw(&t); Chris@113: if (tcsetattr(master, TCSANOW, &t)) { Chris@113: DEBUG << "tcsetattr failed" << endl; Chris@113: perror("tcsetattr failed"); Chris@113: } Chris@111: if (grantpt(master)) { Chris@111: perror("grantpt failed"); Chris@111: } Chris@111: if (unlockpt(master)) { Chris@111: perror("unlockpt failed"); Chris@111: } Chris@111: char *slave = ptsname(master); Chris@111: if (!slave) { Chris@111: perror("ptsname failed"); Chris@111: ::close(master); Chris@111: return; Chris@111: } Chris@111: m_ptyMasterFd = master; Chris@113: m_ptyFile = new QFile(); Chris@113: connect(m_ptyFile, SIGNAL(readyRead()), this, SLOT(dataReadyPty())); Chris@113: if (!m_ptyFile->open(m_ptyMasterFd, QFile::ReadWrite)) { Chris@113: DEBUG << "HgRunner::openTerminal: Failed to open QFile on master fd" << endl; Chris@113: } Chris@111: m_ptySlaveFilename = slave; Chris@111: DEBUG << "HgRunner::openTerminal: succeeded, slave is " Chris@111: << m_ptySlaveFilename << endl; Chris@111: #endif Chris@111: } Chris@111: Chris@111: void HgRunner::closeTerminal() Chris@111: { Chris@84: #ifndef Q_OS_WIN32 Chris@84: if (m_ptySlaveFilename != "") { Chris@113: delete m_ptyFile; Chris@113: m_ptyFile = 0; Chris@84: ::close(m_ptyMasterFd); Chris@84: m_ptySlaveFilename = ""; Chris@84: } Chris@84: #endif jtkorhonen@0: }