fabric/executils.cpp

fabric/executils.cpp

fabric/executils.cpp

Namespaces

Name
Syntalos

Source code

/*
 * Copyright (C) 2016-2026 Matthias Klumpp <matthias@tenstral.net>
 *
 * Licensed under the GNU Lesser General Public License Version 3
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the license, or
 * (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this software.  If not, see <http://www.gnu.org/licenses/>.
 */

#include "executils.h"

#include <QDebug>
#include <QFile>
#include <QApplication>
#include <QFileInfo>
#include <QProcess>
#include <QStandardPaths>
#include <QEventLoop>
#include <glib.h>

#include "simpleterminal.h"
#include "sysinfo.h"
#include "utils/misc.h"

namespace Syntalos
{
QString shellQuote(const QString &str)
{
    auto cs = g_shell_quote(qPrintable(str));
    const auto res = QString::fromUtf8(cs);
    g_free(cs);
    return res;
}

QString findHostExecutable(const QString &exe)
{
    auto sysInfo = SysInfo::get();
    if (sysInfo->inFlatpakSandbox()) {
        QStringList exeLocations = QStringList() << "/usr/bin"
                                                 << "/usr/local/bin"
                                                 << "/usr/sbin"
                                                 << "";
        for (const auto &loc : exeLocations) {
            QString exeHost = QStringLiteral("/run/host%1/%2").arg(loc, exe);
            QFileInfo fi(exeHost);
            if (fi.isExecutable())
                return loc + "/" + exe;
        }
        return QString();
    }

    // no sandbox
    return QStandardPaths::findExecutable(exe);
}

int runHostExecutable(const QString &exe, const QStringList &args, bool waitForFinished)
{
    auto sysInfo = SysInfo::get();
    QProcess proc;
    proc.setProcessChannelMode(QProcess::ForwardedChannels);
    if (sysInfo->inFlatpakSandbox()) {
        // in sandbox, go via flatpak-spawn
        QStringList fpsArgs = QStringList() << "--host";
        fpsArgs << exe;
        if (waitForFinished)
            proc.start("flatpak-spawn", fpsArgs + args);
        else
            proc.startDetached("flatpak-spawn", fpsArgs + args);
    } else {
        // no sandbox, we can run the command directly
        if (waitForFinished)
            proc.start(exe, args);
        else
            proc.startDetached(exe, args);
    }
    if (waitForFinished) {
        proc.waitForFinished(-1);
        return proc.exitCode();
    } else {
        return 0;
    }
}

int runInTerminal(const QString &cmd, const QStringList &args, const QString &wdir, const QString &title)
{
    auto termWin = std::make_unique<SimpleTerminal>();
    if (!title.isEmpty())
        termWin->setWindowTitle(title);

    if (!wdir.isEmpty())
        termWin->setWorkingDirectory(wdir);

    // create our helper script
    auto rtdDir = QStandardPaths::writableLocation(QStandardPaths::RuntimeLocation);
    if (rtdDir.isEmpty())
        rtdDir = "/tmp";
    auto exitFname = QStringLiteral("%1/sy-termexit-%2").arg(rtdDir, createRandomString(6));
    auto shHelperFname = QStringLiteral("%1/sy-termrun-%2").arg(rtdDir, createRandomString(6));

    QFile shFile(shHelperFname);
    if (!shFile.open(QIODevice::WriteOnly | QIODevice::Text)) {
        qWarning().noquote() << "Unable to open temporary file" << shHelperFname << "for writing.";
        return -255;
    }

    QString cmdShell = shellQuote(cmd);
    for (const auto &a : args)
        cmdShell += QStringLiteral(" %1").arg(shellQuote(a));
    QTextStream out(&shFile);
    out << "#!/bin/sh\n" << cmdShell << "\n" << QStringLiteral("echo $? > %1\n").arg(exitFname);
    shFile.flush();
    shFile.setPermissions(QFileDevice::ExeUser | QFileDevice::ReadUser | QFileDevice::WriteUser);
    shFile.close();

    // show the terminal window
    termWin->show();
    termWin->raise();
    termWin->activateWindow();

    // run our helper script as shell
    termWin->setShellProgram(shHelperFname);
    termWin->startShell();

    // wait for the terminal to close
    QEventLoop loop;
    bool finished = false;

    QObject::connect(termWin.get(), &SimpleTerminal::finished, [&finished, &loop]() {
        finished = true;
        loop.quit();
    });
    QObject::connect(termWin.get(), &SimpleTerminal::windowClosed, [&loop]() {
        loop.quit();
    });

    // run the event loop until the command is done or the window is closed
    loop.exec();
    QApplication::processEvents();

    if (finished) {
        // read the exit code from the temp file
        QFile file(exitFname);
        int exitCode = 255;
        if (file.open(QIODevice::ReadOnly | QIODevice::Text)) {
            exitCode = QString::fromUtf8(file.readAll()).trimmed().toInt();
            file.close();
        }
        file.remove();

        return exitCode;
    } else {
        // window was closed before command finished
        QFile::remove(exitFname);
        return std::numeric_limits<int>::max();
    }
}

} // namespace Syntalos

Updated on 2026-02-12 at 17:03:04 +0000