summaryrefslogblamecommitdiffstats
path: root/src/xx.cpp
blob: 1e529aef47750070f0ae5e4641d76f5f9b58281b (plain) (tree)
1
2
3
4
5
6
7
8
9


                     
                 
                 
                          
                  
                   
                             
 

















































                                                                                                   
                        



                                                                  




                                                                                                  
                                                                                       



                                                                                         

                                                                                                                                         
                                            






























































                                                                                   

 
                                                                  









                                                                                                 

                                                                         



                                                                        

                 



                                                                                    




                                               




                                  

                                              




                                                                                      
                                





































                                                                                                             


 




                                             









                                                                             
                                                               

















                                                                                                   

                                                                   

                                                        
                                                                                                  










                                                                          
                                                                                 








































                                                                         










                                                                                                                      












                                                                                                                        
                                                    
                                      
                                      
                                               




                                                     

                                                         
                                    





















                                                                                                                  
                                                          
 





                                                                          













                                                                                       



                                                 



                                                         
 
                                                                                                                 
 







































                                                                                                     
     
                                           









                                                                                              









                                                                                               
                                 










                                                                               

                                                             
                                                          





















                                                                                                       
                                            
 
                                             






                                                           























                                                                   
                    
                     

                                                              



                                                    
         










                                                                                          

                                
                  

 
                                                           
 
                    
                                             
                                       


                                                          
                  

 
                                                                                    
 
                        
                    






                                       

                                

                                                  
                      
              



                                           
                                   



                                                                 



                                                                                            
             
                 
                       

                                
                  






























                                                                                                  












                                                     


                                                                         




                                       





















                                                                                            



                                                                  











                                                                                                   



                                                                                






                                            
#include "xx.h"
#include "xprivate.h"
#include "cvt.h"
#include "main.h"
#include <QDebug>
#include <QSocketNotifier>
#include <QThread>
#include <QProcess>
#include <QRegularExpression>

/*
 * This clusterfuck exists because there are name clashes between X11/Xrandr headers
 * and Qt classes. Hence the split into xx.* and xprivate.* as well as those idiotic
 * matrjoschka classes. Or I'm just stupid.
 */

class BackupInternalInternal
{
private:
    XPrivate *x;
public:
    CrtcMap map;
    BackupInternalInternal() : x(nullptr) {
        qDebug() << "new BackupInternalInternal";
    }
    ~BackupInternalInternal() {
        qDebug() << "delete BackupInternalInternal";
        freeBackup();
    }
    void createBackup(XPrivate *x) {
        this->x = x;
        freeBackup();
        for (CrtcMap::iterator it = x->_crtcMap.begin(); it != x->_crtcMap.end(); ++it) {
            const auto src = it.value();
            XRRCrtcInfo *copy = static_cast<XRRCrtcInfo*>(calloc(1, sizeof(XRRCrtcInfo)));
            copy->outputs = static_cast<RROutput*>(calloc(size_t(src->noutput), sizeof(RROutput)));
            copy->x = src->x;
            copy->y = src->y;
            copy->mode = src->mode;
            copy->rotation = src->rotation;
            copy->noutput = src->noutput;
            for (int i = 0; i < src->noutput; ++i) {
                copy->outputs[i] = src->outputs[i];
            }
            map[it.key()] = copy;
        }
        qDebug() << "Created CRTC backup with entries:" << map.size();
    }
    void freeBackup() {
        for (auto i : map) {
            free(i->outputs);
            free(i);
        }
        map.clear();
    }
    void revertChanges()
    {
        if (map.isEmpty())
            return;
        qDebug() << "Starting revert";
        QStringList cmd;
        QSize screenSize;
        for (auto e : map) {
            if (e->mode == None || !x->_modeMap.contains(e->mode))
                continue;
            XRRModeInfo *mode = x->_modeMap[e->mode];
            QString rate = QString::number(toVertRefresh(mode), 'f', 2);
            for (int i = 0; i < e->noutput; ++i) {
                auto *oi = x->_outputMap[e->outputs[i]];
                cmd << "--output" << oi->outputName << "--mode" << mode->name << "--rate" << rate;
                cmd << "--pos" << QString::asprintf("%dx%d", oi->crtc->x, oi->crtc->y);
                if (oi->crtc->x == 0 && oi->crtc->y == 0 && !cmd.contains("--primary")) {
                    cmd << "--primary";
                }
            }
            screenSize = screenSize.expandedTo(QSize(e->x + int(x->_modeMap[e->mode]->width), e->y + int(x->_modeMap[e->mode]->height)));
        }
        ScreenSetup::inst()->runXrandr(cmd);
        freeBackup();
    }
};

class BackupInternal
{
public:
    BackupInternal() {
        qDebug() << "new BackupInternal";
    }
    ~BackupInternal() {
        qDebug() << "delete BackupInternal";
    }
    QSharedPointer<BackupInternalInternal> backup;
};

ConfigBackup::ConfigBackup()
{
    _ok = false;
    a = new BackupInternal;
}

ConfigBackup::ConfigBackup(XPrivate *x)
{
    _ok = false;
    a = new BackupInternal;
    a->backup = QSharedPointer<BackupInternalInternal>(new BackupInternalInternal);
    a->backup->createBackup(x);
}

ConfigBackup::~ConfigBackup()
{
    delete a;
}

ConfigBackup& ConfigBackup::operator=(const ConfigBackup &other)
{
    if (this == &other)
        return *this;
    delete a;
    _ok = other._ok;
    a = new BackupInternal;
    a->backup = other.a->backup;
    return *this;
}

ConfigBackup::ConfigBackup(const ConfigBackup &other)
{
    _ok = other._ok;
    a = new BackupInternal;
    a->backup = other.a->backup;
}

void ConfigBackup::revert()
{
    a->backup->revertChanges();
}

static QWeakPointer<BackupInternalInternal> currentBackup;

/*
 * Slightly more normal code starts here
 */


ScreenInfo ScreenSetup::initScreenInfo(const OutputInfo *oi) const
{
	ScreenInfo si;
	si.position = oi->position;
	si.name = oi->modelName;
	si.output = oi->outputName;
	si.isProjector = (oi->outputType == Projector::Yes);
	if (oi->mode != nullptr) {
		si.currentResolution = QSize(QSize(int(oi->mode->width), int(oi->mode->height)));
	}
	for (int i = 0; i < oi->output->nmode; ++i) {
		if (a->_modeMap.contains(oi->output->modes[i])) {
			auto m = a->_modeMap.value(oi->output->modes[i]);
			const QSize size(int(m->width), int(m->height));
			if (!si.modes.contains(size)) {
				si.modes.append(size);
			}
		}
	}
	auto pref = a->getPreferredMode(oi);
	if (pref != nullptr) {
		si.preferredResolution = QSize(int(pref->width), int(pref->height));
	}
	return si;
}

ScreenSetup * ScreenSetup::_instance = nullptr;

static int errorHandlerX(Display*)
{
	exit(1);
}

ScreenSetup::ScreenSetup() : a(new XPrivate())
{
	int event_base_return, error_base_return;
	if (!XRRQueryExtension(a->_display, &event_base_return, &error_base_return)) {
		qDebug() << "No XRANDR extension found";
		exit(1);
	}
	updateScreenResources();
	XRRSelectInput(a->_display, DefaultRootWindow(a->_display), RROutputChangeNotifyMask);
	//XSync(a->_display, False);
	XSetIOErrorHandler((XIOErrorHandler) errorHandlerX);
	_socketNotifier = new QSocketNotifier(qintptr(ConnectionNumber(a->_display)), QSocketNotifier::Read);
	connect(_socketNotifier, &QSocketNotifier::activated, [=](int) {
		XEvent ev;
		qDebug() << "Socket Event";
		while (XPending(a->_display) > 0) {
			XNextEvent(a->_display, &ev);
			if (ev.type - event_base_return != RRNotify) {
				qDebug() << "Received unknown X event";
				continue;
			}
			qDebug() << "Got Change Event";
			XRROutputChangeNotifyEvent *oce = reinterpret_cast<XRROutputChangeNotifyEvent*>(&ev);
			XRRScreenResources *sr = XRRGetScreenResources(oce->display, oce->window);
			if (sr == nullptr) {
				emit outputConfigChanged(ConnectionEvent::Unknown);
				continue;
			}
			XRROutputInfo *oi = XRRGetOutputInfo(a->_display, sr, oce->output);
			if (oi == nullptr) {
				XRRFreeScreenResources(sr);
				emit outputConfigChanged(ConnectionEvent::Unknown);
				continue;
			}
			if (oi->connection == RR_Connected) {
				emit outputConfigChanged(ConnectionEvent::Connected);
			} else if (oi->connection == RR_Disconnected) {
				emit outputConfigChanged(ConnectionEvent::Disconnected);
			} else {
				emit outputConfigChanged(ConnectionEvent::Unknown);
			}
			XRRFreeOutputInfo(oi);
			XRRFreeScreenResources(sr);
		}
	});


}

void ScreenSetup::addMissingEdidResolutions()
{
	a->addMissingEdidResolutions();
}

//___________________________________________________________________________
void ScreenSetup::updateScreenResources()
{
	a->updateScreenResources();
}

QMap<QString, ScreenInfo> ScreenSetup::getScreenPositions() const
{
	QMap<QString, ScreenInfo> ret;
	for (auto oi : a->_outputMap) {
		ret.insert(oi->outputName, initScreenInfo(oi));
	}
	return ret;
}

/**
 * Create common modes and add them to all outputs.
 * Make sure every output has some variant of the most commonly
 * used modes.
 */
void ScreenSetup::initModes()
{
	// First copy typical resolutions to all outputs
#define RES(x,y) (((y) << 16) | (x))
	QSet<quint32> wanted;
	wanted << RES(1280, 720) << RES(1280, 800) << RES(1920, 1080);
	for (XRRModeInfo *mode : a->_modeMap) {
		if (toVertRefresh(mode) < 58 || toVertRefresh(mode) > 61)
			continue; // Play it safe and consider only those for copying that are 60Hz
		if (!wanted.remove(RES(mode->width, mode->height)))
			continue; // Is not a wanted resolution
		// Make sure all outputs got it
		for (OutputInfo *info : a->_outputMap) {
            if (!a->getOutputModeForResolution(info->output, mode->width, mode->height).isEmpty())
				continue;
			XRRAddOutputMode(a->_display, info->id, mode->id);
		}
	}
#undef RES
	// Create those that no output supported
	for (auto res : wanted) {
		unsigned int x = res & 0xffff;
		unsigned int y = res >> 16;
		createMode(x, y, 60, QString::asprintf("%ux%u", x, y));
	}
	if (!wanted.isEmpty()) { // Modes were added, update for final loop below
		updateScreenResources();
	}
	// Finally copy all those the projector supports to other outputs
	for (auto key : a->_outputMap.keys()) {
		OutputInfo *oi = a->_outputMap[key];
		if (oi->outputType == Projector::Yes) {
			a->copyModesToAll(key, oi->output->nmode);
		}
	}
	updateScreenResources();
}

static QSize getTotalSizeHorz(const QList<QSize> &list)
{
	QSize ret(0, 0);
	for (auto e : list) {
		ret.rwidth() += e.width();
		ret.rheight() = qMax(ret.height(), e.height());
	}
	return ret;
}

ScreenMode ScreenSetup::getCurrentMode()
{
	bool notAtOrigin = false;
	for (auto oi : a->_outputMap) {
		if (oi->mode != nullptr) {
			if (oi->crtc->x != 0 || oi->crtc->y != 0) {
				notAtOrigin = true;
			}
		}
	}
	if (a->_outputMap.size() == 1)
		return ScreenMode::Single;
	if (a->_outputMap.size() > 2)
		return ScreenMode::Advanced;
	if (notAtOrigin)
		return ScreenMode::Dual;
	return ScreenMode::Clone;
}

bool ScreenSetup::hasScreenWithoutEdid()
{
	for (auto oi : a->_outputMap) {
		// no preferred modes pretty much means no EDID, although technically I think you could have EDID
		// that doesn't provide a preferred mode...
		if (oi->output != nullptr && oi->output->connection != RR_Disconnected && oi->output->npreferred == 0)
			return true;
	}
	return false;
}

ConfigBackup ScreenSetup::setResolutionsFromString(const QString &resolutions, const QString &mapping)
{
	auto resListStr = resolutions.split(QRegularExpression(QLatin1String("\\s+")), QString::SkipEmptyParts);
	QList<QPair<QSize, QList<QString>>> config;
	QRegularExpression re(QLatin1String("^(\\d+)x(\\d+)$"));
	for (auto s : resListStr) {
		auto m = re.match(s);
		if (m.hasMatch()) {
			config.append(qMakePair(QSize(m.captured(1).toInt(), m.captured(2).toInt()), QList<QString>()));
			qDebug() << "Adding resolution" << s;
		}
	}
	auto outputListStr = mapping.split(QRegularExpression(QLatin1String("\\s+")), QString::SkipEmptyParts);
	qDebug() << mapping << ">" << outputListStr;
	if (outputListStr.isEmpty()) {
		QList<QString> sorted;
		for (auto *o : a->_outputMap) {
			sorted.append(o->outputName);
		}
		qSort(sorted);
		int i = 0;
		for (auto o : sorted) {
			int index = i % config.size();
			auto x = config.at(index).second;
			x.append(o);
			config.replace(index, qMakePair(config.at(index).first, x));
			qDebug() << "Resolution" << config.at(index).first << "is now" << config.at(index).second;
			++i;
		}
	} else {
		QRegularExpression re(QLatin1String("^(.+)=(\\d+)$"));
		for (auto s : outputListStr) {
			auto m = re.match(s);
			if (m.hasMatch()) {
				int index = m.captured(2).toInt();
				if (index >= 0 && index < config.size()) {
					auto x = config.at(index).second;
					x.append(m.captured(1));
					config.replace(index, qMakePair(config.at(index).first, x));
				}
			}
		}
	}
	qDebug() << config;
	return setCustom(config);
}

ConfigBackup ScreenSetup::setDefaultMode(ScreenMode &mode)
{
    ConfigBackup retval;
    if (a->_outputMap.size() == 1) { // Only one output exists, do nothing
        retval._ok = (a->_outputMap.begin().value()->mode != nullptr);
        mode = ScreenMode::Single;
        return retval;
    }
	QMap<QString, OutputInfo*> screenMap;
	QMap<QString, OutputInfo*> projectorMap;
	for (auto o : a->_outputMap) {
		qDebug() << o->outputName << quint32(o->outputType);
		if (o->outputType == Projector::Yes) {
			projectorMap.insert(o->outputName, o);
		} else {
			screenMap.insert(o->outputName, o);
		}
	}
	auto projectors = projectorMap.values();
	auto screens = screenMap.values();
	qDebug() << projectors.size() << "projectors," << screens.size() << "screens.";
	QList<QSize> outputSizes = a->getTotalSize(projectors, screens);
    if (outputSizes.isEmpty()) {
        mode = ScreenMode::Advanced; // Dunno lol
        return retval;
    }
    for (;;) {
        QSize screenSize = getTotalSizeHorz(outputSizes);
        QStringList cmd;
        retval = createCrtcBackup();

        qDebug() << "Virtual screen size:" << screenSize << "with" << outputSizes.size() << "different screens.";

        int offset = 0;
        for (int i = 0; i < outputSizes.size(); ++i) {
            const QSize &size = outputSizes.at(i);
            unsigned int w = 0;
            if (i < projectors.size()) {
                   auto *mode = a->setOutputResolution(cmd, projectors.at(i), offset, 0, size);
                    if (mode != nullptr && mode->width > w) {
                        w = mode->width;
                    }
            }
            if (i < screens.size()) {
                   auto *mode = a->setOutputResolution(cmd, screens.at(i), offset, 0, size);
                    if (mode != nullptr && mode->width > w) {
                        w = mode->width;
                    }
            }
            offset += w;
        }
        retval._ok = runXrandr(cmd);
        if (retval._ok)
            break;
        // If xrandr failed, try again while capping the resolution of any screen to FullHD
        bool keepGoing = false;
        for (QSize &geo : outputSizes) {
            if (geo.width() > 1920) {
                keepGoing = true;
                geo.setWidth(1920);
            }
            if (geo.height() > 1200) {
                keepGoing = true;
                geo.setHeight(1080);
            }
        }
        // If we didn't clamp resolutions, but have more than one output group, remove last and retry
        if (!keepGoing && outputSizes.size() > 1) {
            keepGoing = true;
            outputSizes.removeLast();
        }
        if (!keepGoing)
            break; // No more options
    }
	updateScreenResources(); // Re-Read
    if (outputSizes.size() == 1) {
        // One output size, at least 2 outputs in total -- clone mode
        mode = ScreenMode::Clone;
    } else if (outputSizes.size() == 2 && a->_outputMap.size() == 2) {
        // Two outputs, two sizes -- extended
        mode = ScreenMode::Dual;
    } else {
        mode = ScreenMode::Advanced; // Must be more than 2 outputs -> something more involved
    }
    return retval;
}

//___________________________________________________________________________
bool ScreenSetup::createMode(unsigned int resX, unsigned int resY, float refresh, QString name)
{
	QByteArray ba = name.toLocal8Bit();
	mode *mode = vert_refresh(int(resX), int(resY), refresh, 0, 0, 0);
	if (mode == nullptr)
		return false;
	XRRModeInfo m;
	memset(&m, 0, sizeof(m));
	m.width     = static_cast<unsigned int>(mode->hr);
	m.height    = static_cast<unsigned int>(mode->vr);
	m.dotClock  = static_cast<unsigned long>(mode->pclk) * 1000ul * 1000ul;
	m.hSyncStart= static_cast<unsigned int>(mode->hss);
	m.hSyncEnd  = static_cast<unsigned int>(mode->hse);
	m.hTotal    = static_cast<unsigned int>(mode->hfl);
	m.hSkew     = 0;
	m.vSyncStart= static_cast<unsigned int>(mode->vss);
	m.vSyncEnd  = static_cast<unsigned int>(mode->vse);
	m.vTotal    = static_cast<unsigned int>(mode->vfl);
	m.id        = 0;
	m.name      = ba.data();
	m.nameLength= static_cast<unsigned int>(ba.length());
	m.modeFlags = RR_VSyncPositive | RR_HSyncNegative;
	free(mode);

	for (XRRModeInfo *mode : a->_modeMap) {
		if (mode->width == m.width && mode->height == m.height && mode->dotClock == m.dotClock)
			return true; // Already exists, return true?
	}

	RRMode xid = XRRCreateMode(a->_display, DefaultRootWindow(a->_display), &m);
	qDebug() << "Return value of create was" << xid;
	// Immediately add to all screens
	for (OutputInfo *info : a->_outputMap) {
		XRRAddOutputMode(a->_display, info->id, xid);
	}
	return true;
}

//___________________________________________________________________________
ScreenSetup::~ScreenSetup()
{
	delete a;
}

ConfigBackup ScreenSetup::setCenteredClone()
{
    ConfigBackup retval = createCrtcBackup();
	XRRModeInfo *fallback = nullptr;
	for (auto m : a->_modeMap) {
		if (m->width == 1024 && m->height == 768) {
			fallback = m;
			break;
		}
	}
	// See if we even need to do anything
	for (int i = 0; i < 2; ++i) {
		int withPref = 0;
		QSet<RRMode> known;
		for (auto oi : a->_outputMap) {
			if (oi->output->npreferred == 0) {
				if (oi->mode != nullptr) {
					known.insert(oi->mode->id);
				}
			} else {
				withPref++;
				known.insert(oi->output->modes[0]);
			}
		}
		if (withPref == 0 && i == 0) {
			QThread::msleep(100);
			updateScreenResources();
			retval = createCrtcBackup();
			continue;
		}
		if (known.count() < 2)
			return retval;
		break;
	}
    QStringList cmd;
    QSize screenSize;
	for (auto oi : a->_outputMap) {
		auto mode = a->getPreferredMode(oi, fallback);
        if (mode == nullptr)
            continue;
        if (int(mode->width) > screenSize.width()) {
            screenSize.setWidth(int(mode->width));
        }
        if (int(mode->height) > screenSize.height()) {
            screenSize.setHeight(int(mode->height));
        }
    }
    for (auto oi : a->_outputMap) {
        auto mode = a->getPreferredMode(oi, fallback);
        if (mode == nullptr)
            continue;
        const int x = (screenSize.width() - int(mode->width)) / 2;
        const int y = (screenSize.height() - int(mode->height)) / 2;
        a->setOutputResolution(cmd, oi, x, y, QSize(int(mode->width), int(mode->height)));
    }
    retval._ok = runXrandr(cmd);
    return retval;
}

ConfigBackup ScreenSetup::setClone(const QSize &resolution)
{
    QStringList cmd;
    ConfigBackup retval = createCrtcBackup();
	for (auto oi : a->_outputMap) {
        a->setOutputResolution(cmd, oi, 0, 0, resolution);
    }
    retval._ok = runXrandr(cmd);
    return retval;
}

ConfigBackup ScreenSetup::setCustom(const QList<QPair<QSize, QList<QString>>> &list)
{
    ConfigBackup retval;
    QStringList cmd;
	QList<QSize> sizes;
	for (auto e : list) {
		if (e.second.isEmpty())
			continue;
		sizes.append(e.first);
	}
	if (sizes.isEmpty())
        return retval;
    retval = createCrtcBackup();
	auto screenSize = getTotalSizeHorz(sizes);
	if (screenSize.isEmpty())
        return retval;
    int x = 0;
	for (auto e : list) {
		if (e.second.isEmpty())
			continue;
		const QSize &res = e.first;
		unsigned int w = 0;
		for (auto outputName : e.second) {
			for (auto oi : a->_outputMap) {
				if (oi->outputName != outputName)
					continue;
				    auto *mode = a->setOutputResolution(cmd, oi, x, 0, res);
					 if (mode != nullptr && mode->width > w) {
						 w = mode->width;
					 }
            }
		}
		x += w;
    }
    retval._ok = runXrandr(cmd);
    return retval;
}

static bool modeBiggerThan(const QSize &a, const QSize &b)
{
	if (a.width() > b.width())
		return true;
	return a.width() == b.width() && a.height() > b.height();
}

ResolutionVector ScreenSetup::getCommonModes() const
{
	QHash<QPair<quint32, quint32>, QSet<RROutput>> matches;
	for (auto oi : a->_outputMap) {
		for (int i = 0; i < oi->output->nmode; ++i) {
			if (!a->_modeMap.contains(oi->output->modes[i]))
				continue;
			const auto mode = a->_modeMap[oi->output->modes[i]];
			const QPair<quint32, quint32> pair = qMakePair(mode->width, mode->height);
			matches[pair].insert(oi->id);
		}
	}
	ResolutionVector ret;
	for (auto it = matches.begin(); it != matches.end(); ++it) {
		if (it.value().size() == a->_outputMap.size()) {
			ret.append(QSize(int(it.key().first), int(it.key().second)));
		}
	}
	qSort(ret.begin(), ret.end(), modeBiggerThan);
	return ret;
}

ConfigBackup ScreenSetup::createCrtcBackup()
{
    auto bd = currentBackup.toStrongRef();
    if (bd.data() != nullptr && !bd->map.isEmpty()) {
        ConfigBackup backup;
        backup.a->backup = bd;
        return backup;
    }
    ConfigBackup backup(a);
    currentBackup = backup.a->backup.toWeakRef();
    return backup;
}

/**
 * Return number of connected (no active) outputs according to last query
 */
int ScreenSetup::getOutputCount() const
{
	return a->_outputMap.size();
}

/**
 * Query currently connected number of outputs
 */
int ScreenSetup::queryCurrentOutputCount() const
{
	auto sr = XRRGetScreenResourcesCurrent(a->_display, DefaultRootWindow(a->_display));
	if (sr == nullptr)
		return 0;
	int count = 0;
	for (int i = 0; i < sr->noutput; ++i) {
		XRROutputInfo* info = XRRGetOutputInfo(a->_display, sr, sr->outputs[i]);
		if (info == nullptr)
			continue;
		if (info->connection != RR_Disconnected) {
			count++;
		}
		XRRFreeOutputInfo(info);
	}
	XRRFreeScreenResources(sr);
	return count;
}

const ResolutionVector &ScreenSetup::getVirtualResolutions() const
{
	return a->_resolutions;
}

bool ScreenSetup::runXrandr(QStringList &cmd)
{
    QProcess proc;
    // Sloppy: Turn off all outputs not found in argument list. Doesn't actually parse the
    // command line, so if you have a mode that's called like an output, funny things might happen.
    for (const auto &name : a->_allOutputs) {
        if (!cmd.contains(name)) {
            cmd << "--output" << name << "--off";
        }
    }
    qDebug() << "XRANDR:" << cmd;
    if (CommandLine::testMode()) {
        cmd << "--dryrun";
    }
    proc.setProcessChannelMode(QProcess::ProcessChannelMode::ForwardedChannels);
    proc.start("xrandr", cmd);
    proc.waitForFinished(5000);
    if (proc.state() == QProcess::Running) {
        proc.kill();
    }
    return proc.exitCode() == 0;
}