maemoqemumanager.cpp 18.2 KB
Newer Older
1 2 3 4
/**************************************************************************
**
** This file is part of Qt Creator
**
hjk's avatar
hjk committed
5
** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies).
6
**
Eike Ziller's avatar
Eike Ziller committed
7
** Contact: http://www.qt-project.org/
8 9 10 11
**
**
** GNU Lesser General Public License Usage
**
hjk's avatar
hjk committed
12 13 14 15 16 17
** This file may be used under the terms of the GNU Lesser General Public
** License version 2.1 as published by the Free Software Foundation and
** appearing in the file LICENSE.LGPL included in the packaging of this file.
** Please review the following information to ensure the GNU Lesser General
** Public License version 2.1 requirements will be met:
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
18
**
con's avatar
con committed
19
** In addition, as a special exception, Nokia gives you certain additional
hjk's avatar
hjk committed
20
** rights. These rights are described in the Nokia Qt LGPL Exception
con's avatar
con committed
21 22
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
hjk's avatar
hjk committed
23 24 25 26 27
** Other Usage
**
** Alternatively, this file may be used in accordance with the terms and
** conditions contained in a signed written agreement between you and Nokia.
**
28 29 30
**
**************************************************************************/

ck's avatar
ck committed
31
#include "maemoqemumanager.h"
32

33
#include "maemoglobal.h"
34
#include "maemoqemuruntimeparser.h"
35
#include "maemosettingspages.h"
dt's avatar
dt committed
36
#include "maemoqtversion.h"
37 38 39

#include <coreplugin/actionmanager/actionmanager.h>
#include <coreplugin/actionmanager/command.h>
40
#include <coreplugin/id.h>
41 42
#include <coreplugin/coreconstants.h>
#include <coreplugin/icore.h>
43
#include <coreplugin/icontext.h>
44 45
#include <coreplugin/modemanager.h>

Tobias Hunger's avatar
Tobias Hunger committed
46
#include <projectexplorer/buildconfiguration.h>
47
#include <projectexplorer/projectexplorer.h>
48
#include <projectexplorer/project.h>
49
#include <projectexplorer/session.h>
Tobias Hunger's avatar
Tobias Hunger committed
50 51
#include <projectexplorer/target.h>
#include <qtsupport/qtprofileinformation.h>
52
#include <qtsupport/qtversionmanager.h>
53
#include <remotelinux/remotelinuxrunconfiguration.h>
54
#include <utils/filesystemwatcher.h>
55

56 57 58 59 60
#include <QDebug>
#include <QDir>
#include <QList>
#include <QSet>
#include <QStringBuilder>
61

62 63 64
#include <QAction>
#include <QDesktopServices>
#include <QMessageBox>
65

66 67
#include <limits.h>

68
using namespace ProjectExplorer;
69
using namespace RemoteLinux;
70 71 72

namespace Madde {
namespace Internal {
73

ck's avatar
ck committed
74
MaemoQemuManager *MaemoQemuManager::m_instance = 0;
75 76 77

const QSize iconSize = QSize(24, 20);

ck's avatar
ck committed
78
MaemoQemuManager::MaemoQemuManager(QObject *parent)
79 80 81
    : QObject(parent)
    , m_qemuAction(0)
    , m_qemuProcess(new QProcess(this))
82
    , m_runningQtId(INT_MIN)
83
    , m_userTerminated(false)
84 85
    , m_runtimeRootWatcher(0)
    , m_runtimeFolderWatcher(0)
86 87 88 89 90
{
    m_qemuStarterIcon.addFile(":/qt-maemo/images/qemu-run.png", iconSize);
    m_qemuStarterIcon.addFile(":/qt-maemo/images/qemu-stop.png", iconSize,
        QIcon::Normal, QIcon::On);

91
    m_qemuAction = new QAction("MeeGo Emulator", this);
92
    m_qemuAction->setIcon(m_qemuStarterIcon.pixmap(iconSize));
93
    m_qemuAction->setToolTip(tr("Start MeeGo Emulator"));
94 95
    connect(m_qemuAction, SIGNAL(triggered()), this, SLOT(startRuntime()));

Eike Ziller's avatar
Eike Ziller committed
96
    Core::Command *qemuCommand = Core::ActionManager::registerAction(m_qemuAction,
97
        "MaemoEmulator", Core::Context(Core::Constants::C_GLOBAL));
98 99 100
    qemuCommand->setAttribute(Core::Command::CA_UpdateText);
    qemuCommand->setAttribute(Core::Command::CA_UpdateIcon);

101
    Core::ModeManager::addAction(qemuCommand->action(), 1);
102 103
    m_qemuAction->setEnabled(false);
    m_qemuAction->setVisible(false);
104 105

    // listen to qt version changes to update the start button
106 107
    connect(QtSupport::QtVersionManager::instance(), SIGNAL(qtVersionsChanged(QList<int>,QList<int>,QList<int>)),
        this, SLOT(qtVersionsChanged(QList<int>,QList<int>,QList<int>)));
108 109 110 111 112 113 114 115 116 117 118 119

    // listen to project add, remove and startup changes to udate start button
    SessionManager *session = ProjectExplorerPlugin::instance()->session();
    connect(session, SIGNAL(projectAdded(ProjectExplorer::Project*)), this,
        SLOT(projectAdded(ProjectExplorer::Project*)));
    connect(session, SIGNAL(projectRemoved(ProjectExplorer::Project*)), this,
        SLOT(projectRemoved(ProjectExplorer::Project*)));
    connect(session, SIGNAL(startupProjectChanged(ProjectExplorer::Project*)),
        this, SLOT(projectChanged(ProjectExplorer::Project*)));

    connect(m_qemuProcess, SIGNAL(error(QProcess::ProcessError)), this,
        SLOT(qemuProcessError(QProcess::ProcessError)));
Robert Loehning's avatar
Robert Loehning committed
120
    connect(m_qemuProcess, SIGNAL(finished(int,QProcess::ExitStatus)), this,
121
        SLOT(qemuProcessFinished()));
ck's avatar
ck committed
122 123 124 125
    connect(m_qemuProcess, SIGNAL(readyReadStandardOutput()), this,
        SLOT(qemuOutput()));
    connect(m_qemuProcess, SIGNAL(readyReadStandardError()), this,
        SLOT(qemuOutput()));
Robert Loehning's avatar
Robert Loehning committed
126 127
    connect(this, SIGNAL(qemuProcessStatus(QemuStatus,QString)),
        this, SLOT(qemuStatusChanged(QemuStatus,QString)));
128
}
129

130
Utils::FileSystemWatcher *MaemoQemuManager::runtimeRootWatcher()
131 132
{
    if (!m_runtimeRootWatcher) {
133 134
        m_runtimeRootWatcher = new Utils::FileSystemWatcher(this);
        m_runtimeRootWatcher->setObjectName(QLatin1String("MaemoQemuRuntimeRootWatcher"));
135 136 137 138 139 140
        connect(m_runtimeRootWatcher, SIGNAL(directoryChanged(QString)), this,
            SLOT(runtimeRootChanged(QString)));
    }
    return m_runtimeRootWatcher;
}

141
Utils::FileSystemWatcher *MaemoQemuManager::runtimeFolderWatcher()
142 143
{
    if (!m_runtimeFolderWatcher) {
144 145
        m_runtimeFolderWatcher = new Utils::FileSystemWatcher(this);
        m_runtimeFolderWatcher->setObjectName(QLatin1String("MaemoQemuRuntimeFolderWatcher"));
146 147 148 149
        connect(m_runtimeFolderWatcher, SIGNAL(directoryChanged(QString)), this,
            SLOT(runtimeFolderChanged(QString)));
    }
    return m_runtimeFolderWatcher;
150 151
}

ck's avatar
ck committed
152
MaemoQemuManager::~MaemoQemuManager()
153 154
{
    terminateRuntime();
155
    m_instance = 0;
156 157
}

ck's avatar
ck committed
158
MaemoQemuManager &MaemoQemuManager::instance(QObject *parent)
159
{
160
    if (m_instance == 0)
ck's avatar
ck committed
161
        m_instance = new MaemoQemuManager(parent);
162 163 164
    return *m_instance;
}

165
bool MaemoQemuManager::runtimeForQtVersion(int uniqueId, MaemoQemuRuntime *rt) const
166
{
167
    *rt = m_runtimes.value(uniqueId, MaemoQemuRuntime());
168
    return rt->isValid();
169 170
}

171 172 173 174 175
bool MaemoQemuManager::qemuIsRunning() const
{
    return m_runningQtId != INT_MIN;
}

176
void MaemoQemuManager::qtVersionsChanged(const QList<int> &added, const QList<int> &removed, const QList<int> &changed)
177
{
178 179
    QList<int> uniqueIds;
    uniqueIds << added << removed << changed;
180
    QtSupport::QtVersionManager *manager = QtSupport::QtVersionManager::instance();
181 182
    foreach (int uniqueId, uniqueIds) {
        if (manager->isValidId(uniqueId)) {
dt's avatar
dt committed
183 184 185
            MaemoQtVersion *version = dynamic_cast<MaemoQtVersion *>(manager->version(uniqueId));

            if (version) {
186 187
                MaemoQemuRuntime runtime
                    = MaemoQemuRuntimeParser::parseRuntime(version);
188
                if (runtime.isValid()) {
189
                    m_runtimes.insert(uniqueId, runtime);
190 191 192
                    if (!runtimeRootWatcher()->watchesDirectory(runtime.m_watchPath))
                        runtimeRootWatcher()->addDirectory(runtime.m_watchPath,
                                                           Utils::FileSystemWatcher::WatchAllChanges);
193 194 195
                } else {
                    m_runtimes.remove(uniqueId);
                }
196 197 198 199 200 201 202 203 204 205 206 207
            }
        } else {
            // this qt version has been removed from the settings
            m_runtimes.remove(uniqueId);
            if (uniqueId == m_runningQtId) {
                terminateRuntime();
                emit qemuProcessStatus(QemuUserReason, tr("Qemu has been shut "
                    "down, because you removed the corresponding Qt version."));
            }
        }
    }

208
    showOrHideQemuButton();
209 210
}

ck's avatar
ck committed
211
void MaemoQemuManager::projectAdded(ProjectExplorer::Project *project)
212 213 214 215 216 217 218 219 220 221 222 223 224
{
    // handle all target related changes, add, remove, etc...
    connect(project, SIGNAL(addedTarget(ProjectExplorer::Target*)), this,
        SLOT(targetAdded(ProjectExplorer::Target*)));
    connect(project, SIGNAL(removedTarget(ProjectExplorer::Target*)), this,
        SLOT(targetRemoved(ProjectExplorer::Target*)));
    connect(project, SIGNAL(activeTargetChanged(ProjectExplorer::Target*)),
        this, SLOT(targetChanged(ProjectExplorer::Target*)));

    foreach (Target *target, project->targets())
        targetAdded(target);
}

ck's avatar
ck committed
225
void MaemoQemuManager::projectRemoved(ProjectExplorer::Project *project)
226 227 228 229 230 231 232 233 234 235
{
    disconnect(project, SIGNAL(addedTarget(ProjectExplorer::Target*)), this,
        SLOT(targetAdded(ProjectExplorer::Target*)));
    disconnect(project, SIGNAL(removedTarget(ProjectExplorer::Target*)), this,
        SLOT(targetRemoved(ProjectExplorer::Target*)));
    disconnect(project, SIGNAL(activeTargetChanged(ProjectExplorer::Target*)),
        this, SLOT(targetChanged(ProjectExplorer::Target*)));

    foreach (Target *target, project->targets())
        targetRemoved(target);
236
    showOrHideQemuButton();
237 238
}

ck's avatar
ck committed
239
void MaemoQemuManager::projectChanged(ProjectExplorer::Project *project)
240
{
241
    if (project) {
242
        toggleStarterButton(project->activeTarget());
243 244
        deviceConfigurationChanged(project->activeTarget());
    }
245 246
}

ck's avatar
ck committed
247
void MaemoQemuManager::targetAdded(ProjectExplorer::Target *target)
248
{
Tobias Hunger's avatar
Tobias Hunger committed
249
    if (!target || !MaemoGlobal::hasMaemoDevice(target->profile()))
250 251 252 253
        return;

    // handle the qt version changes the build configuration uses
    connect(target, SIGNAL(environmentChanged()), this, SLOT(environmentChanged()));
Tobias Hunger's avatar
Tobias Hunger committed
254
    connect(target, SIGNAL(profileChanged()), this, SLOT(systemChanged()));
255

256
    toggleStarterButton(target);
257 258
}

ck's avatar
ck committed
259
void MaemoQemuManager::targetRemoved(ProjectExplorer::Target *target)
260
{
Tobias Hunger's avatar
Tobias Hunger committed
261
    if (!target || !MaemoGlobal::hasMaemoDevice(target->profile()))
262 263 264
        return;

    disconnect(target, SIGNAL(environmentChanged()), this, SLOT(environmentChanged()));
Tobias Hunger's avatar
Tobias Hunger committed
265
    disconnect(target, SIGNAL(profileChanged()), this, SLOT(systemChanged()));
266

267
    showOrHideQemuButton();
268 269
}

ck's avatar
ck committed
270
void MaemoQemuManager::targetChanged(ProjectExplorer::Target *target)
271
{
272
    if (target) {
273
        toggleStarterButton(target);
274 275
        deviceConfigurationChanged(target);
    }
276 277
}

Tobias Hunger's avatar
Tobias Hunger committed
278
void MaemoQemuManager::systemChanged()
279
{
Tobias Hunger's avatar
Tobias Hunger committed
280 281
    Target *t = qobject_cast<Target *>(sender());
    targetChanged(t);
282 283
}

ck's avatar
ck committed
284
void MaemoQemuManager::environmentChanged()
285 286 287 288
{
    // likely to happen when the qt version changes the build config is using
    if (ProjectExplorerPlugin *explorer = ProjectExplorerPlugin::instance()) {
        if (Project *project = explorer->session()->startupProject())
289
            toggleStarterButton(project->activeTarget());
290 291 292
    }
}

ck's avatar
ck committed
293
void MaemoQemuManager::deviceConfigurationChanged(ProjectExplorer::Target *target)
294
{
ck's avatar
ck committed
295
    m_qemuAction->setEnabled(targetUsesMatchingRuntimeConfig(target));
296 297
}

ck's avatar
ck committed
298
void MaemoQemuManager::startRuntime()
299 300 301 302 303
{
    m_userTerminated = false;
    Project *p = ProjectExplorerPlugin::instance()->session()->startupProject();
    if (!p)
        return;
304
    QtSupport::BaseQtVersion *version;
ck's avatar
ck committed
305 306
    if (!targetUsesMatchingRuntimeConfig(p->activeTarget(), &version)) {
        qWarning("Strange: Qemu button was enabled, but target does not match.");
307
        return;
ck's avatar
ck committed
308
    }
309

ck's avatar
ck committed
310
    m_runningQtId = version->uniqueId();
311
    const MaemoQemuRuntime rt = m_runtimes.value(version->uniqueId());
312
    m_qemuProcess->setProcessEnvironment(rt.environment());
ck's avatar
ck committed
313
    m_qemuProcess->setWorkingDirectory(rt.m_root);
314
    m_qemuProcess->start(rt.m_bin % QLatin1Char(' ') % rt.m_args);
ck's avatar
ck committed
315 316
    if (!m_qemuProcess->waitForStarted())
        return;
317

ck's avatar
ck committed
318 319 320
    emit qemuProcessStatus(QemuStarting);
    connect(m_qemuAction, SIGNAL(triggered()), this, SLOT(terminateRuntime()));
    disconnect(m_qemuAction, SIGNAL(triggered()), this, SLOT(startRuntime()));
321 322
}

ck's avatar
ck committed
323
void MaemoQemuManager::terminateRuntime()
324 325 326 327 328 329 330 331 332 333 334 335
{
    m_userTerminated = true;

    if (m_qemuProcess->state() != QProcess::NotRunning) {
        m_qemuProcess->terminate();
        m_qemuProcess->kill();
    }

    connect(m_qemuAction, SIGNAL(triggered()), this, SLOT(startRuntime()));
    disconnect(m_qemuAction, SIGNAL(triggered()), this, SLOT(terminateRuntime()));
}

ck's avatar
ck committed
336
void MaemoQemuManager::qemuProcessFinished()
337
{
338
    m_runningQtId = INT_MIN;
339
    QemuStatus status = QemuFinished;
ck's avatar
ck committed
340
    QString error;
341 342

    if (!m_userTerminated) {
ck's avatar
ck committed
343 344 345 346 347 348 349
        if (m_qemuProcess->exitStatus() == QProcess::CrashExit) {
            status = QemuCrashed;
            error = m_qemuProcess->errorString();
        } else if (m_qemuProcess->exitCode() != 0) {
            error = tr("Qemu finished with error: Exit code was %1.")
                .arg(m_qemuProcess->exitCode());
        }
350 351 352
    }

    m_userTerminated = false;
ck's avatar
ck committed
353
    emit qemuProcessStatus(status, error);
354 355
}

ck's avatar
ck committed
356
void MaemoQemuManager::qemuProcessError(QProcess::ProcessError error)
357 358 359 360 361
{
    if (error == QProcess::FailedToStart)
        emit qemuProcessStatus(QemuFailedToStart, m_qemuProcess->errorString());
}

ck's avatar
ck committed
362
void MaemoQemuManager::qemuStatusChanged(QemuStatus status, const QString &error)
363 364 365 366 367 368 369
{
    bool running = false;
    switch (status) {
        case QemuStarting:
            running = true;
            break;
        case QemuFailedToStart:
370 371
            QMessageBox::warning(0, tr("Qemu error"),
                tr("Qemu failed to start: %1"));
372
            break;
373
        case QemuCrashed:
374
            MaemoQemuSettingsPage::showQemuCrashDialog();
375 376 377
            break;
        case QemuFinished:
        case QemuUserReason:
378 379
            if (!error.isEmpty())
                QMessageBox::warning(0, tr("Qemu error"), error);
380 381 382 383 384 385 386 387
            break;
        default:
            Q_ASSERT(!"Missing handling of Qemu status");
    }

    updateStarterIcon(running);
}

ck's avatar
ck committed
388
void MaemoQemuManager::qemuOutput()
ck's avatar
ck committed
389 390 391 392 393
{
    qDebug("%s", m_qemuProcess->readAllStandardOutput().data());
    qDebug("%s", m_qemuProcess->readAllStandardError().data());
}

394 395 396
void MaemoQemuManager::runtimeRootChanged(const QString &directory)
{
    QList<int> uniqueIds;
397
    QMap<int, MaemoQemuRuntime>::const_iterator it;
398 399 400 401 402 403
    for (it = m_runtimes.constBegin(); it != m_runtimes.constEnd(); ++it) {
        if (QDir(it.value().m_watchPath) == QDir(directory))
            uniqueIds.append(it.key());
    }

    foreach (int uniqueId, uniqueIds) {
404
        MaemoQemuRuntime runtime = m_runtimes.value(uniqueId, MaemoQemuRuntime());
405 406 407 408 409 410 411 412 413 414
        if (runtime.isValid()) {
            if (QFile::exists(runtime.m_root)) {
                // nothing changed, so we can remove it
                uniqueIds.removeAll(uniqueId);
            }
        } else {
            if (QFile::exists(runtime.m_root)) {
                if (!QFile::exists(runtime.m_root + QLatin1String("/information"))) {
                    // install might be still in progress
                    uniqueIds.removeAll(uniqueId);
415 416
                    runtimeFolderWatcher()->addDirectory(runtime.m_root,
                                                         Utils::FileSystemWatcher::WatchAllChanges);
417 418 419 420 421 422 423 424 425 426 427
                }
            }
        }
    }
    notify(uniqueIds);
}

void MaemoQemuManager::runtimeFolderChanged(const QString &directory)
{
    if (QFile::exists(directory + QLatin1String("/information"))) {
        QList<int> uniqueIds;
428
        QMap<int, MaemoQemuRuntime>::const_iterator it;
429 430 431 432 433
        for (it = m_runtimes.constBegin(); it != m_runtimes.constEnd(); ++it) {
            if (QDir(it.value().m_root) == QDir(directory))
                uniqueIds.append(it.key());
        }
        notify(uniqueIds);
434
        if (m_runtimeFolderWatcher)
435
            m_runtimeFolderWatcher->removeDirectory(directory);
436 437 438
    }
}

439 440
// -- private

ck's avatar
ck committed
441
void MaemoQemuManager::updateStarterIcon(bool running)
442 443 444 445 446
{
    QIcon::State state;
    QString toolTip;
    if (running) {
        state = QIcon::On;
447
        toolTip = tr("Stop MeeGo Emulator");
448 449
    } else {
        state = QIcon::Off;
450
        toolTip = tr("Start MeeGo Emulator");
451 452 453 454 455 456 457
    }

    m_qemuAction->setToolTip(toolTip);
    m_qemuAction->setIcon(m_qemuStarterIcon.pixmap(iconSize, QIcon::Normal,
        state));
}

ck's avatar
ck committed
458
void MaemoQemuManager::toggleStarterButton(Target *target)
459 460 461
{
    int uniqueId = -1;
    if (target) {
Tobias Hunger's avatar
Tobias Hunger committed
462 463 464
        QtSupport::BaseQtVersion *version = QtSupport::QtProfileInformation::qtVersion(target->profile());
        if (version)
            uniqueId = version->uniqueId();
465 466
    }

467
    if (uniqueId >= 0 && (m_runtimes.isEmpty() || !m_runtimes.contains(uniqueId)))
468
        qtVersionsChanged(QList<int>(), QList<int>(), QList<int>() << uniqueId);
469

470 471 472 473
    bool isRunning = m_qemuProcess->state() != QProcess::NotRunning;
    if (m_runningQtId == uniqueId)
        isRunning = false;

474 475 476
    const Project * const p
        = ProjectExplorerPlugin::instance()->session()->startupProject();
    const bool qemuButtonEnabled
Tobias Hunger's avatar
Tobias Hunger committed
477
        = p && p->activeTarget() && MaemoGlobal::hasMaemoDevice(target->profile())
478 479 480 481
            && m_runtimes.value(uniqueId, MaemoQemuRuntime()).isValid()
            && targetUsesMatchingRuntimeConfig(target) && !isRunning;
    m_qemuAction->setEnabled(qemuButtonEnabled);
    showOrHideQemuButton();
482 483
}

ck's avatar
ck committed
484
bool MaemoQemuManager::sessionHasMaemoTarget() const
485 486 487
{
    ProjectExplorerPlugin *explorer = ProjectExplorerPlugin::instance();
    const QList<Project*> &projects = explorer->session()->projects();
488 489
    foreach (const Project *p, projects) {
        foreach (const Target * const target, p->targets()) {
Tobias Hunger's avatar
Tobias Hunger committed
490
            if (MaemoGlobal::hasMaemoDevice(target->profile()))
491 492 493 494
                return true;
        }
    }
    return false;
495 496
}

ck's avatar
ck committed
497
bool MaemoQemuManager::targetUsesMatchingRuntimeConfig(Target *target,
498
    QtSupport::BaseQtVersion **qtVersion)
499 500 501
{
    if (!target)
        return false;
502 503
    if (target != target->project()->activeTarget())
        return false;
504

505 506
    RemoteLinuxRunConfiguration *mrc =
        qobject_cast<RemoteLinuxRunConfiguration *> (target->activeRunConfiguration());
ck's avatar
ck committed
507 508
    if (!mrc)
        return false;
Tobias Hunger's avatar
Tobias Hunger committed
509 510

    QtSupport::BaseQtVersion *version = QtSupport::QtProfileInformation::qtVersion(target->profile());
511
    if (!version || !m_runtimes.value(version->uniqueId(), MaemoQemuRuntime()).isValid())
ck's avatar
ck committed
512 513
        return false;

ck's avatar
ck committed
514 515
    if (qtVersion)
        *qtVersion = version;
516 517
    const IDevice::ConstPtr config = DeviceProfileInformation::device(target->profile());
    return !config.isNull() && config->machineType() == IDevice::Emulator;
518 519
}

520 521
void MaemoQemuManager::notify(const QList<int> uniqueIds)
{
522
    qtVersionsChanged(QList<int>(), QList<int>(), uniqueIds);
523 524 525
    environmentChanged();   // to toggle the start button
}

526 527 528 529 530 531 532
void MaemoQemuManager::showOrHideQemuButton()
{
    const bool showButton = !m_runtimes.isEmpty() && sessionHasMaemoTarget();
    if (!showButton)
        terminateRuntime();
    m_qemuAction->setVisible(showButton);
}
533 534 535

}   // namespace Internal
}   // namespace Madde