summaryrefslogtreecommitdiffstats
path: root/tools
diff options
context:
space:
mode:
Diffstat (limited to 'tools')
-rw-r--r--tools/runonphone/codasignalhandler.cpp579
-rw-r--r--tools/runonphone/codasignalhandler.h118
-rw-r--r--tools/runonphone/main.cpp192
-rw-r--r--tools/runonphone/runonphone.pro12
-rw-r--r--tools/runonphone/symbianutils/codadevice.cpp1501
-rw-r--r--tools/runonphone/symbianutils/codadevice.h447
-rw-r--r--tools/runonphone/symbianutils/codamessage.cpp (renamed from tools/runonphone/symbianutils/tcftrkmessage.cpp)177
-rw-r--r--tools/runonphone/symbianutils/codamessage.h (renamed from tools/runonphone/symbianutils/tcftrkmessage.h)133
-rw-r--r--tools/runonphone/symbianutils/json.cpp87
-rw-r--r--tools/runonphone/symbianutils/json.h8
-rw-r--r--tools/runonphone/symbianutils/launcher.cpp202
-rw-r--r--tools/runonphone/symbianutils/launcher.h29
-rw-r--r--tools/runonphone/symbianutils/symbiandevicemanager.cpp290
-rw-r--r--tools/runonphone/symbianutils/symbiandevicemanager.h78
-rw-r--r--tools/runonphone/symbianutils/symbianutils.pri18
-rw-r--r--tools/runonphone/symbianutils/tcftrkdevice.cpp929
-rw-r--r--tools/runonphone/symbianutils/tcftrkdevice.h295
-rw-r--r--tools/runonphone/symbianutils/trkutils.h4
-rw-r--r--tools/runonphone/symbianutils/virtualserialdevice.cpp89
-rw-r--r--tools/runonphone/symbianutils/virtualserialdevice.h127
-rw-r--r--tools/runonphone/symbianutils/virtualserialdevice_posix.cpp344
-rw-r--r--tools/runonphone/symbianutils/virtualserialdevice_win.cpp369
-rw-r--r--tools/runonphone/trksignalhandler.cpp8
-rw-r--r--tools/runonphone/trksignalhandler.h4
24 files changed, 4506 insertions, 1534 deletions
diff --git a/tools/runonphone/codasignalhandler.cpp b/tools/runonphone/codasignalhandler.cpp
new file mode 100644
index 0000000..0d086b5
--- /dev/null
+++ b/tools/runonphone/codasignalhandler.cpp
@@ -0,0 +1,579 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the tools applications of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** GNU Lesser General Public License Usage
+** 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.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU General
+** Public License version 3.0 as published by the Free Software Foundation
+** and appearing in the file LICENSE.GPL included in the packaging of this
+** file. Please review the following information to ensure the GNU General
+** Public License version 3.0 requirements will be met:
+** http://www.gnu.org/copyleft/gpl.html.
+**
+** 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.
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include <QCoreApplication>
+#include <QDebug>
+#include <QDir>
+#include <QFile>
+#include <QObject>
+#include <QTimer>
+#include "codasignalhandler.h"
+
+static const quint64 DEFAULT_CHUNK_SIZE = 40000;
+
+class CodaSignalHandlerPrivate
+{
+ friend class CodaSignalHandler;
+public:
+ CodaSignalHandlerPrivate();
+ ~CodaSignalHandlerPrivate();
+private:
+ SymbianUtils::CodaDevicePtr codaDevice;
+ QEventLoop *eventLoop;
+ QTextStream out;
+ QTextStream err;
+ int loglevel;
+ int timeout;
+ int result;
+ CodaAction action;
+ QString copySrcFileName;
+ QString copyDstFileName;
+ QString dlSrcFileName;
+ QString dlDstFileName;
+ QString appFileName;
+ QString commandLineArgs;
+ QString serialPortName;
+ QString appID;
+ QByteArray remoteFileHandle;
+ QScopedPointer<QFile> localFile;
+ QScopedPointer<QFile> remoteFile;
+ quint64 putChunkSize;
+ quint64 putLastChunkSize;
+ quint64 remoteBytesLeft;
+ quint64 remoteFileSize;
+ bool putWriteOk;
+ bool connected;
+ bool debugSessionControl;
+};
+
+CodaSignalHandlerPrivate::CodaSignalHandlerPrivate()
+ : eventLoop(0),
+ out(stdout),
+ err(stderr),
+ loglevel(0),
+ timeout(0),
+ result(0),
+ action(ActionPingOnly),
+ putChunkSize(DEFAULT_CHUNK_SIZE),
+ putLastChunkSize(0),
+ remoteBytesLeft(0),
+ remoteFileSize(0),
+ putWriteOk(false),
+ connected(false),
+ debugSessionControl(false)
+{
+}
+
+CodaSignalHandlerPrivate::~CodaSignalHandlerPrivate()
+{
+ delete eventLoop;
+ out.flush();
+ err.flush();
+}
+
+void CodaSignalHandler::error(const QString &errorMessage)
+{
+ reportError(tr("CODA error: %1").arg(errorMessage));
+}
+
+void CodaSignalHandler::logMessage(const QString &logMessage)
+{
+ reportMessage(tr("CODA log: %1").arg(logMessage));
+}
+
+void CodaSignalHandler::serialPong(const QString &codaVersion)
+{
+ reportMessage(tr("CODA version: %1").arg(codaVersion));
+}
+
+void CodaSignalHandler::tcfEvent(const Coda::CodaEvent &event)
+{
+ reportMessage(tr("CODA event: Type: %1 Message: %2").arg(event.type()).arg(event.toString()));
+
+ switch (event.type()) {
+ case Coda::CodaEvent::LocatorHello:
+ handleConnected(event);
+ break;
+ case Coda::CodaEvent::ProcessExitedEvent:
+ handleAppExited(event);
+ break;
+ default:
+ reportMessage(tr("CODA unhandled event: Type: %1 Message: %2").arg(event.type()).arg(event.toString()));
+ break;
+ }
+}
+
+void CodaSignalHandler::terminate()
+{
+ if (d->codaDevice) {
+ disconnect(d->codaDevice.data(), 0, this, 0);
+ SymbianUtils::SymbianDeviceManager::instance()->releaseCodaDevice(d->codaDevice);
+ }
+}
+
+void CodaSignalHandler::finished()
+{
+ if (d->eventLoop)
+ d->eventLoop->exit();
+}
+
+void CodaSignalHandler::timeout()
+{
+ reportError(tr("Unable to connect to CODA device. Operation timed out."));
+}
+
+int CodaSignalHandler::run()
+{
+ d->codaDevice = SymbianUtils::SymbianDeviceManager::instance()->getCodaDevice(d->serialPortName);
+ bool ok = d->codaDevice && d->codaDevice->device()->isOpen();
+ if (!ok) {
+ QString deviceError = "No such port";
+ if (d->codaDevice)
+ deviceError = d->codaDevice->device()->errorString();
+ reportError(tr("Could not open serial device: ").arg(deviceError));
+ SymbianUtils::SymbianDeviceManager::instance()->releaseCodaDevice(d->codaDevice);
+ return 1;
+ }
+
+ if (d->loglevel > 1) {
+ d->codaDevice->setVerbose(1);
+ }
+
+ connect(d->codaDevice.data(), SIGNAL(error(const QString &)), this, SLOT(error(const QString &)));
+ connect(d->codaDevice.data(), SIGNAL(logMessage(const QString &)), this, SLOT(logMessage(const QString &)));
+ connect(d->codaDevice.data(), SIGNAL(serialPong(const QString &)), this, SLOT(serialPong(const QString &)));
+ connect(d->codaDevice.data(), SIGNAL(tcfEvent(const Coda::CodaEvent &)), this, SLOT(tcfEvent(const Coda::CodaEvent &)));
+ connect(this, SIGNAL(done()), this, SLOT(finished()));
+
+ d->codaDevice->sendSerialPing(false);
+ if (d->timeout > 0)
+ QTimer::singleShot(d->timeout, this, SLOT(timeout()));
+ d->eventLoop = new QEventLoop();
+ d->eventLoop->exec();
+ int result = d->result;
+ reportMessage(tr("Done."));
+ disconnect(d->codaDevice.data(), 0, this, 0);
+ SymbianUtils::SymbianDeviceManager::instance()->releaseCodaDevice(d->codaDevice);
+
+ QCoreApplication::quit();
+ return result;
+}
+
+void CodaSignalHandler::setActionType(CodaAction action)
+{
+ d->action = CodaAction(d->action | action);
+}
+
+void CodaSignalHandler::setAppFileName(const QString &fileName)
+{
+ d->appFileName = fileName;
+}
+
+void CodaSignalHandler::setCodaDevice(SymbianUtils::CodaDevicePtr &codaDevice)
+{
+ d->codaDevice = codaDevice;
+}
+
+void CodaSignalHandler::setCommandLineArgs(const QString &args)
+{
+ d->commandLineArgs = args;
+}
+
+void CodaSignalHandler::setCopyFileName(const QString &srcName, const QString &dstName)
+{
+ d->copySrcFileName = srcName;
+ d->copyDstFileName = dstName;
+}
+
+void CodaSignalHandler::setDownloadFileName(const QString &srcName, const QString &dstName)
+{
+ d->dlSrcFileName = srcName;
+ d->dlDstFileName = dstName;
+}
+
+void CodaSignalHandler::setLogLevel(int level)
+{
+ d->loglevel = level;
+}
+
+void CodaSignalHandler::setSerialPortName(const QString &serialPortName)
+{
+ d->serialPortName = serialPortName;
+}
+
+void CodaSignalHandler::setTimeout(const int msec)
+{
+ d->timeout = msec;
+}
+
+void CodaSignalHandler::closeFile()
+{
+ d->remoteFile.reset();
+ if (!d->codaDevice) {
+ emit done();
+ return;
+ }
+
+ d->codaDevice->sendFileSystemCloseCommand(Coda::CodaCallback(this, &CodaSignalHandler::handleFileSystemClose),
+ d->remoteFileHandle);
+}
+
+void CodaSignalHandler::handleConnected(const Coda::CodaEvent &event)
+{
+ if (d->connected)
+ return;
+
+ const Coda::CodaLocatorHelloEvent &hEvent = static_cast<const Coda::CodaLocatorHelloEvent &>(event);
+ QStringList services = hEvent.services();
+ if (services.contains("DebugSessionControl")) {
+ d->debugSessionControl = true;
+ }
+ d->connected = true;
+ handleActions();
+}
+
+void CodaSignalHandler::handleActions()
+{
+ if (d->action & ActionCopy) {
+ initFileSending();
+ } else if (d->action & ActionInstall) {
+ initFileInstallation();
+ } else if (d->action & ActionRun) {
+ initAppRunning();
+ } else if (d->action & ActionDownload) {
+ initFileDownloading();
+ } else {
+ emit done();
+ }
+}
+
+void CodaSignalHandler::handleAppExited(const Coda::CodaEvent &event)
+{
+ const Coda::CodaProcessExitedEvent &pEvent = static_cast<const Coda::CodaProcessExitedEvent &>(event);
+ QString id = pEvent.idString();
+ if (!id.compare(d->appID, Qt::CaseInsensitive)) {
+ d->codaDevice->sendDebugSessionControlSessionEndCommand(Coda::CodaCallback(this, &CodaSignalHandler::handleDebugSessionControlEnd));
+ d->action = static_cast<CodaAction>(d->action & ~ActionRun);
+ handleActions();
+ }
+}
+
+void CodaSignalHandler::handleAppRunning(const Coda::CodaCommandResult &result)
+{
+ if (result.type == Coda::CodaCommandResult::SuccessReply) {
+ reportMessage(tr("Running..."));
+
+ Coda::JsonValue value = result.values.at(0);
+ readAppId(value);
+ } else {
+ reportError(tr("Launch failed: %1").arg(result.toString()));
+ }
+}
+
+void CodaSignalHandler::handleDebugSessionControlEnd(const Coda::CodaCommandResult &result)
+{
+ if (result.type == Coda::CodaCommandResult::SuccessReply) {
+ // nothing to do
+ }
+}
+
+void CodaSignalHandler::handleDebugSessionControlStart(const Coda::CodaCommandResult &result)
+{
+ if (result.type == Coda::CodaCommandResult::SuccessReply) {
+ d->codaDevice->sendRunProcessCommand(Coda::CodaCallback(this, &CodaSignalHandler::handleAppRunning),
+ d->appFileName.toAscii(),
+ d->commandLineArgs.split(' '));
+ reportMessage(tr("Launching %1...").arg(QFileInfo(d->appFileName).fileName()));
+ } else {
+ reportError(tr("Failed to start CODA debug session control."));
+ }
+}
+
+void CodaSignalHandler::handleFileSystemClose(const Coda::CodaCommandResult &result)
+{
+ if (result.type == Coda::CodaCommandResult::SuccessReply) {
+ reportMessage(tr("File closed."));
+ if (d->action & ActionCopy) {
+ d->action = static_cast<CodaAction>(d->action & ~ActionCopy);
+ } else if (d->action & ActionDownload) {
+ d->action = static_cast<CodaAction>(d->action & ~ActionDownload);
+ }
+ handleActions();
+ } else {
+ reportError(tr("Failed to close the remote file: %1").arg(result.toString()));
+ }
+}
+
+void CodaSignalHandler::handleFileSystemOpen(const Coda::CodaCommandResult &result)
+{
+ if (result.type != Coda::CodaCommandResult::SuccessReply) {
+ reportError(tr("Could not open remote file: %1").arg(result.errorString()));
+ return;
+ }
+
+ if (result.values.size() < 1 || result.values.at(0).data().isEmpty()) {
+ reportError(tr("Internal error: No filehandle obtained"));
+ return;
+ }
+
+ if (d->action & ActionCopy) {
+ d->remoteFileHandle = result.values.at(0).data();
+ d->remoteFile.reset(new QFile(d->copySrcFileName));
+ if (!d->remoteFile->open(QIODevice::ReadOnly)) { // Should not fail, was checked before
+ reportError(tr("Could not open local file %1").arg(d->copySrcFileName));
+ return;
+ }
+ putSendNextChunk();
+ } else if (d->action & ActionDownload) {
+ d->remoteFileHandle = result.values.at(0).data();
+ d->localFile.reset(new QFile(d->dlDstFileName));
+ // remove any existing file with the same name
+ if (d->localFile->exists() && !d->localFile->remove()) {
+ reportError(tr("Could not create host file: %1 due to error: %2").arg(d->localFile->fileName(), d->localFile->errorString()));
+ return;
+ }
+ // open local file in write-only mode
+ if (!d->localFile->open(QFile::WriteOnly)) {
+ reportError(tr("Could not open host file for writing: %1 due to error: %2").arg(d->localFile->fileName(), d->localFile->errorString()));
+ return;
+ }
+ d->codaDevice->sendFileSystemFstatCommand(Coda::CodaCallback(this, &CodaSignalHandler::handleFileSystemStart),
+ d->remoteFileHandle);
+ }
+}
+
+void CodaSignalHandler::handleFileSystemRead(const Coda::CodaCommandResult &result)
+{
+ if (result.type != Coda::CodaCommandResult::SuccessReply || result.values.size() != 2) {
+ reportError(tr("Could not read remote file: %1").arg(result.errorString()));
+ return;
+ }
+
+ QByteArray data = QByteArray::fromBase64(result.values.at(0).data());
+ bool eof = result.values.at(1).toVariant().toBool();
+ qint64 written = d->localFile->write(data);
+ if (written < 0) {
+ reportError(tr("Could not write data to host file: %1 due to error: %2").arg(d->localFile->fileName(), d->localFile->errorString()));
+ return;
+ }
+
+ d->remoteBytesLeft -= written;
+ if (!eof && d->remoteBytesLeft > 0) {
+ readNextChunk();
+ }
+ else {
+ d->localFile->flush();
+ d->localFile->close();
+ closeFile();
+ }
+}
+
+void CodaSignalHandler::handleFileSystemStart(const Coda::CodaCommandResult &result)
+{
+ if (result.type != Coda::CodaCommandResult::SuccessReply) {
+ reportError(tr("Could not open remote file: %1").arg(result.errorString()));
+ return;
+ }
+
+ if (result.values.size() < 1 || result.values.at(0).children().isEmpty()) {
+ reportError(tr("Could not get file attributes"));
+ return;
+ }
+
+ Coda::JsonValue val = result.values.at(0).findChild("Size");
+ d->remoteFileSize = val.isValid() ? val.data().toLong() : -1L;
+ if (d->remoteFileSize < 0) {
+ reportError(tr("Could not get file size"));
+ return;
+ }
+
+ d->remoteBytesLeft = d->remoteFileSize;
+ readNextChunk();
+}
+
+void CodaSignalHandler::handleFileSystemWrite(const Coda::CodaCommandResult &result)
+{
+ // Close remote file even if copy fails
+ d->putWriteOk = result;
+ if (!d->putWriteOk) {
+ QString fileName = QFileInfo(d->copyDstFileName).fileName();
+ reportError(tr("Could not write to file %1 on device: %2").arg(fileName).arg(result.errorString()));
+ }
+
+ if (!d->putWriteOk || d->putLastChunkSize < d->putChunkSize) {
+ closeFile();
+ } else {
+ putSendNextChunk();
+ }
+}
+
+void CodaSignalHandler::handleSymbianInstall(const Coda::CodaCommandResult &result)
+{
+ if (result.type == Coda::CodaCommandResult::SuccessReply) {
+ reportMessage(tr("Installation has finished."));
+ d->action = static_cast<CodaAction>(d->action & ~ActionInstall);
+ handleActions();
+ } else {
+ reportError(tr("Installation failed: %1").arg(result.errorString()));
+ }
+}
+
+void CodaSignalHandler::initAppRunning()
+{
+ if (!d->codaDevice || d->appFileName.isEmpty()) {
+ emit done();
+ return;
+ }
+
+ if (!d->debugSessionControl) {
+ reportError(tr("CODA DebugSessionControl service not found, please update to CODA v4.0.23 or later."));
+ }
+
+ d->codaDevice->sendDebugSessionControlSessionStartCommand(Coda::CodaCallback(this, &CodaSignalHandler::handleDebugSessionControlStart));
+}
+
+void CodaSignalHandler::initFileDownloading()
+{
+ if (!d->codaDevice || d->dlSrcFileName.isEmpty()) {
+ emit done();
+ return;
+ }
+
+ d->codaDevice->sendFileSystemOpenCommand(Coda::CodaCallback(this, &CodaSignalHandler::handleFileSystemOpen),
+ d->dlSrcFileName.toAscii(), Coda::CodaDevice::FileSystem_TCF_O_READ);
+ reportMessage(tr("Downloading %1...").arg(QFileInfo(d->dlSrcFileName).fileName()));
+}
+
+void CodaSignalHandler::initFileInstallation()
+{
+ if (!d->codaDevice || d->copyDstFileName.isEmpty()) {
+ emit done();
+ return;
+ }
+
+ QString installationDrive = "C";
+ d->codaDevice->sendSymbianInstallSilentInstallCommand(Coda::CodaCallback(this, &CodaSignalHandler::handleSymbianInstall),
+ d->copyDstFileName.toAscii(),
+ installationDrive.toAscii());
+ reportMessage(tr("Installing package \"%1\" on drive %2...").arg(QFileInfo(d->copyDstFileName).fileName(), installationDrive));
+}
+
+void CodaSignalHandler::initFileSending()
+{
+ if (!d->codaDevice || d->copySrcFileName.isEmpty()) {
+ emit done();
+ return;
+ }
+
+ const unsigned flags =
+ Coda::CodaDevice::FileSystem_TCF_O_WRITE
+ |Coda::CodaDevice::FileSystem_TCF_O_CREAT
+ |Coda::CodaDevice::FileSystem_TCF_O_TRUNC;
+ d->putWriteOk = false;
+ d->codaDevice->sendFileSystemOpenCommand(Coda::CodaCallback(this, &CodaSignalHandler::handleFileSystemOpen),
+ d->copyDstFileName.toAscii(), flags);
+ reportMessage(tr("Copying %1...").arg(QFileInfo(d->copyDstFileName).fileName()));
+}
+
+void CodaSignalHandler::putSendNextChunk()
+{
+ if (!d->codaDevice || !d->remoteFile) {
+ emit done();
+ return;
+ }
+
+ // Read and send off next chunk
+ const quint64 pos = d->remoteFile->pos();
+ const QByteArray data = d->remoteFile->read(d->putChunkSize);
+ if (data.isEmpty()) {
+ d->putWriteOk = true;
+ closeFile();
+ } else {
+ d->putLastChunkSize = data.size();
+ d->codaDevice->sendFileSystemWriteCommand(Coda::CodaCallback(this, &CodaSignalHandler::handleFileSystemWrite),
+ d->remoteFileHandle, data, unsigned(pos));
+ }
+}
+
+void CodaSignalHandler::readNextChunk()
+{
+ const quint64 pos = d->remoteFileSize - d->remoteBytesLeft;
+ const quint64 size = qMin(d->remoteBytesLeft, DEFAULT_CHUNK_SIZE);
+ d->codaDevice->sendFileSystemReadCommand(Coda::CodaCallback(this, &CodaSignalHandler::handleFileSystemRead),
+ d->remoteFileHandle, pos, size);
+}
+
+void CodaSignalHandler::readAppId(Coda::JsonValue value)
+{
+ if (value.isObject()) {
+ Coda::JsonValue idValue = value.findChild("ID");
+ if (idValue.isString()) {
+ d->appID = idValue.data();
+ return;
+ }
+ }
+
+ reportError(tr("Could not get process ID of %1.").arg(QFileInfo(d->appFileName).fileName()));
+}
+
+void CodaSignalHandler::reportError(const QString &message)
+{
+ d->err << message << endl;
+ d->result = 1;
+ emit done();
+}
+
+void CodaSignalHandler::reportMessage(const QString &message)
+{
+ if (d->loglevel > 0)
+ d->out << message << endl;
+}
+
+CodaSignalHandler::CodaSignalHandler()
+ : d(new CodaSignalHandlerPrivate())
+{
+}
+
+CodaSignalHandler::~CodaSignalHandler()
+{
+ delete d;
+}
+
diff --git a/tools/runonphone/codasignalhandler.h b/tools/runonphone/codasignalhandler.h
new file mode 100644
index 0000000..a978bfa
--- /dev/null
+++ b/tools/runonphone/codasignalhandler.h
@@ -0,0 +1,118 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the tools applications of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** GNU Lesser General Public License Usage
+** 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.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU General
+** Public License version 3.0 as published by the Free Software Foundation
+** and appearing in the file LICENSE.GPL included in the packaging of this
+** file. Please review the following information to ensure the GNU General
+** Public License version 3.0 requirements will be met:
+** http://www.gnu.org/copyleft/gpl.html.
+**
+** 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.
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef CODASIGNALHANDLER_H
+#define CODASIGNALHANDLER_H
+
+#include "symbianutils/codamessage.h"
+#include "symbianutils/symbiandevicemanager.h"
+
+#include "symbianutils/codadevice.h"
+
+enum CodaAction {
+ ActionPingOnly = 0x0,
+ ActionCopy = 0x1,
+ ActionInstall = 0x2,
+ ActionCopyInstall = ActionCopy | ActionInstall,
+ ActionRun = 0x4,
+ ActionDownload = 0x8,
+ ActionCopyRun = ActionCopy | ActionRun,
+ ActionInstallRun = ActionInstall | ActionRun,
+ ActionCopyInstallRun = ActionCopy | ActionInstall | ActionRun
+};
+
+class CodaSignalHandlerPrivate;
+class CodaSignalHandler : public QObject
+{
+ Q_OBJECT
+public slots:
+ void error(const QString &errorMessage);
+ void logMessage(const QString &logMessage);
+ void serialPong(const QString &codaVersion);
+ void tcfEvent(const Coda::CodaEvent &event);
+ void terminate();
+private slots:
+ void finished();
+ void timeout();
+signals:
+ void done();
+public:
+ CodaSignalHandler();
+ ~CodaSignalHandler();
+ void init();
+ int run();
+ void setActionType(CodaAction action);
+ void setAppFileName(const QString &fileName);
+ void setCodaDevice(SymbianUtils::CodaDevicePtr &codeDevice);
+ void setCommandLineArgs(const QString &args);
+ void setCopyFileName(const QString &srcName, const QString &dstName);
+ void setDownloadFileName(const QString &srcName, const QString &dstName);
+ void setLogLevel(int level);
+ void setSerialPortName(const QString &serialPortName);
+ void setTimeout(const int msec);
+private:
+ void closeFile();
+ void handleConnected(const Coda::CodaEvent &event);
+ void handleActions();
+ void handleAppExited(const Coda::CodaEvent &event);
+ void handleAppRunning(const Coda::CodaCommandResult &result);
+ void handleDebugSessionControlEnd(const Coda::CodaCommandResult &result);
+ void handleDebugSessionControlStart(const Coda::CodaCommandResult &result);
+ void handleFileSystemClose(const Coda::CodaCommandResult &result);
+ void handleFileSystemOpen(const Coda::CodaCommandResult &result);
+ void handleFileSystemRead(const Coda::CodaCommandResult &result);
+ void handleFileSystemStart(const Coda::CodaCommandResult &result);
+ void handleFileSystemWrite(const Coda::CodaCommandResult &result);
+ void handleSymbianInstall(const Coda::CodaCommandResult &result);
+ void initAppRunning();
+ void initFileDownloading();
+ void initFileInstallation();
+ void initFileSending();
+ void putSendNextChunk();
+ void readNextChunk();
+ void readAppId(Coda::JsonValue value);
+ void reportError(const QString &message);
+ void reportMessage(const QString &message);
+
+ CodaSignalHandlerPrivate *d;
+};
+
+#endif // CODESIGNALHANDLER_H
diff --git a/tools/runonphone/main.cpp b/tools/runonphone/main.cpp
index d749106..6610908 100644
--- a/tools/runonphone/main.cpp
+++ b/tools/runonphone/main.cpp
@@ -45,10 +45,13 @@
#include <QScopedPointer>
#include <QTimer>
#include <QFileInfo>
+#include "symbianutils/codadevice.h"
#include "symbianutils/trkutils.h"
#include "symbianutils/trkdevice.h"
#include "symbianutils/launcher.h"
+#include "symbianutils/symbiandevicemanager.h"
+#include "codasignalhandler.h"
#include "trksignalhandler.h"
#include "serenum.h"
#include "ossignalconverter.h"
@@ -62,14 +65,16 @@ void printUsage(QTextStream& outstream, QString exeName)
<< "-t, --timeout <milliseconds> terminate test if timeout occurs" << endl
<< "-v, --verbose show debugging output" << endl
<< "-q, --quiet hide progress messages" << endl
- << "-u, --upload <local file> <remote file> upload file to phone" << endl
+ << "-u, --upload <local file> upload executable file to phone" << endl
<< "-d, --download <remote file> <local file> copy file from phone to PC after running test" << endl
<< "--nocrashlog Don't capture call stack if test crashes" << endl
<< "--crashlogpath <dir> Path to save crash logs (default=working dir)" << endl
+ << "--coda Use CODA instead of TRK (default agent)" << endl
<< endl
<< "USB COM ports can usually be autodetected, use -p or -f to force a specific port." << endl
+ << "TRK is the default debugging agent, use --coda option when using CODA instead of TRK." << endl
<< "If using System TRK, it is possible to copy the program directly to sys/bin on the phone." << endl
- << "-s can be used with both System and Application TRK to install the program" << endl;
+ << "-s can be used with both System and Application TRK/CODA to install the program" << endl;
}
#define CHECK_PARAMETER_EXISTS if(!it.hasNext()) { printUsage(outstream, args[0]); return 1; }
@@ -86,12 +91,12 @@ int main(int argc, char *argv[])
QTextStream outstream(stdout);
QTextStream errstream(stderr);
QString uploadLocalFile;
- QString uploadRemoteFile;
QString downloadRemoteFile;
QString downloadLocalFile;
int loglevel=1;
int timeout=0;
bool crashlog = true;
+ bool coda = false;
QString crashlogpath;
QListIterator<QString> it(args);
it.next(); //skip name of program
@@ -122,14 +127,21 @@ int main(int argc, char *argv[])
errstream << "Executable file (" << uploadLocalFile << ") doesn't exist" << endl;
return 1;
}
- CHECK_PARAMETER_EXISTS
- uploadRemoteFile = it.next();
+ if (!(QFileInfo(uploadLocalFile).suffix() == "exe")) {
+ errstream << "File (" << uploadLocalFile << ") must be an executable" << endl;
+ return 1;
+ }
}
else if (arg == "--download" || arg == "-d") {
CHECK_PARAMETER_EXISTS
downloadRemoteFile = it.next();
CHECK_PARAMETER_EXISTS
downloadLocalFile = it.next();
+ QFileInfo downloadInfo(downloadLocalFile);
+ if (downloadInfo.exists() && !downloadInfo.isFile()) {
+ errstream << downloadLocalFile << " is not a file." << endl;
+ return 1;
+ }
}
else if (arg == "--timeout" || arg == "-t") {
CHECK_PARAMETER_EXISTS
@@ -140,6 +152,8 @@ int main(int argc, char *argv[])
return 1;
}
}
+ else if (arg == "--coda")
+ coda = true;
else if (arg == "--verbose" || arg == "-v")
loglevel=2;
else if (arg == "--quiet" || arg == "-q")
@@ -160,8 +174,7 @@ int main(int argc, char *argv[])
}
}
- if (exeFile.isEmpty() && sisFile.isEmpty() &&
- (uploadLocalFile.isEmpty() || uploadRemoteFile.isEmpty()) &&
+ if (exeFile.isEmpty() && sisFile.isEmpty() && uploadLocalFile.isEmpty() &&
(downloadLocalFile.isEmpty() || downloadRemoteFile.isEmpty())) {
printUsage(outstream, args[0]);
return 1;
@@ -200,76 +213,117 @@ int main(int argc, char *argv[])
}
}
+ CodaSignalHandler codaHandler;
QScopedPointer<trk::Launcher> launcher;
- launcher.reset(new trk::Launcher(trk::Launcher::ActionPingOnly));
- QFileInfo exeInfo(exeFile);
+ TrkSignalHandler trkHandler;
+ QFileInfo info(exeFile);
QFileInfo uploadInfo(uploadLocalFile);
- if (!sisFile.isEmpty()) {
- launcher->addStartupActions(trk::Launcher::ActionCopyInstall);
- launcher->setCopyFileName(sisFile, "c:\\data\\testtemp.sis");
- launcher->setInstallFileName("c:\\data\\testtemp.sis");
- }
- else if (!uploadLocalFile.isEmpty() && uploadInfo.exists()) {
- launcher->addStartupActions(trk::Launcher::ActionCopy);
- launcher->setCopyFileName(uploadLocalFile, uploadRemoteFile);
- }
- if (!exeFile.isEmpty()) {
- launcher->addStartupActions(trk::Launcher::ActionRun);
- launcher->setFileName(QString("c:\\sys\\bin\\") + exeInfo.fileName());
- launcher->setCommandLineArgs(cmdLine);
- }
- if (!downloadRemoteFile.isEmpty() && !downloadLocalFile.isEmpty()) {
- launcher->addStartupActions(trk::Launcher::ActionDownload);
- launcher->setDownloadFileName(downloadRemoteFile, downloadLocalFile);
- }
- if (loglevel > 0)
- outstream << "Connecting to target via " << serialPortName << endl;
- launcher->setTrkServerName(serialPortName);
- if (loglevel > 1)
- launcher->setVerbose(1);
+ if (coda) {
+ codaHandler.setSerialPortName(serialPortName);
+ codaHandler.setLogLevel(loglevel);
- TrkSignalHandler handler;
- handler.setLogLevel(loglevel);
- handler.setCrashLogging(crashlog);
- handler.setCrashLogPath(crashlogpath);
+ if (!sisFile.isEmpty()) {
+ codaHandler.setActionType(ActionCopyInstall);
+ QString dstName = "c:\\data\\testtemp.sis";
+ codaHandler.setCopyFileName(sisFile, dstName);
+ }
+ else if (!uploadLocalFile.isEmpty() && uploadInfo.exists()) {
+ codaHandler.setActionType(ActionCopy);
+ QString dstName = QString("c:\\sys\\bin\\") + uploadInfo.fileName();
+ codaHandler.setCopyFileName(uploadLocalFile, dstName);
+ }
+ if (!exeFile.isEmpty()) {
+ codaHandler.setActionType(ActionRun);
+ codaHandler.setAppFileName(QString("c:\\sys\\bin\\") + info.fileName());
+ codaHandler.setCommandLineArgs(cmdLine.join(QLatin1String(", ")));
+ }
+ if (!downloadRemoteFile.isEmpty() && !downloadLocalFile.isEmpty()) {
+ codaHandler.setActionType(ActionDownload);
+ codaHandler.setDownloadFileName(downloadRemoteFile, downloadLocalFile);
+ }
- QObject::connect(launcher.data(), SIGNAL(copyingStarted()), &handler, SLOT(copyingStarted()));
- QObject::connect(launcher.data(), SIGNAL(canNotConnect(const QString &)), &handler, SLOT(canNotConnect(const QString &)));
- QObject::connect(launcher.data(), SIGNAL(canNotCreateFile(const QString &, const QString &)), &handler, SLOT(canNotCreateFile(const QString &, const QString &)));
- QObject::connect(launcher.data(), SIGNAL(canNotWriteFile(const QString &, const QString &)), &handler, SLOT(canNotWriteFile(const QString &, const QString &)));
- QObject::connect(launcher.data(), SIGNAL(canNotCloseFile(const QString &, const QString &)), &handler, SLOT(canNotCloseFile(const QString &, const QString &)));
- QObject::connect(launcher.data(), SIGNAL(installingStarted()), &handler, SLOT(installingStarted()));
- QObject::connect(launcher.data(), SIGNAL(canNotInstall(const QString &, const QString &)), &handler, SLOT(canNotInstall(const QString &, const QString &)));
- QObject::connect(launcher.data(), SIGNAL(installingFinished()), &handler, SLOT(installingFinished()));
- QObject::connect(launcher.data(), SIGNAL(startingApplication()), &handler, SLOT(startingApplication()));
- QObject::connect(launcher.data(), SIGNAL(applicationRunning(uint)), &handler, SLOT(applicationRunning(uint)));
- QObject::connect(launcher.data(), SIGNAL(canNotRun(const QString &)), &handler, SLOT(canNotRun(const QString &)));
- QObject::connect(launcher.data(), SIGNAL(applicationOutputReceived(const QString &)), &handler, SLOT(applicationOutputReceived(const QString &)));
- QObject::connect(launcher.data(), SIGNAL(copyProgress(int)), &handler, SLOT(copyProgress(int)));
- QObject::connect(launcher.data(), SIGNAL(stateChanged(int)), &handler, SLOT(stateChanged(int)));
- QObject::connect(launcher.data(), SIGNAL(processStopped(uint,uint,uint,QString)), &handler, SLOT(stopped(uint,uint,uint,QString)));
- QObject::connect(launcher.data(), SIGNAL(libraryLoaded(trk::Library)), &handler, SLOT(libraryLoaded(trk::Library)));
- QObject::connect(launcher.data(), SIGNAL(libraryUnloaded(trk::Library)), &handler, SLOT(libraryUnloaded(trk::Library)));
- QObject::connect(launcher.data(), SIGNAL(registersAndCallStackReadComplete(const QList<uint> &,const QByteArray &)), &handler, SLOT(registersAndCallStackReadComplete(const QList<uint> &,const QByteArray &)));
- QObject::connect(&handler, SIGNAL(resume(uint,uint)), launcher.data(), SLOT(resumeProcess(uint,uint)));
- QObject::connect(&handler, SIGNAL(terminate()), launcher.data(), SLOT(terminate()));
- QObject::connect(&handler, SIGNAL(getRegistersAndCallStack(uint,uint)), launcher.data(), SLOT(getRegistersAndCallStack(uint,uint)));
- QObject::connect(launcher.data(), SIGNAL(finished()), &handler, SLOT(finished()));
+ if (loglevel > 0)
+ outstream << "Connecting to target via " << serialPortName << endl;
- QObject::connect(OsSignalConverter::instance(), SIGNAL(terminate()), launcher.data(), SLOT(terminate()), Qt::QueuedConnection);
+ if (timeout > 0)
+ codaHandler.setTimeout(timeout);
- QTimer timer;
- timer.setSingleShot(true);
- QObject::connect(&timer, SIGNAL(timeout()), &handler, SLOT(timeout()));
- if (timeout > 0) {
- timer.start(timeout);
- }
+ QObject::connect(OsSignalConverter::instance(), SIGNAL(terminate()), &codaHandler, SLOT(terminate()), Qt::QueuedConnection);
+ return codaHandler.run();
- QString errorMessage;
- if (!launcher->startServer(&errorMessage)) {
- errstream << errorMessage << endl;
- return 1;
+ } else {
+ launcher.reset(new trk::Launcher(trk::Launcher::ActionPingOnly));
+ if (!sisFile.isEmpty()) {
+ launcher->addStartupActions(trk::Launcher::ActionCopyInstall);
+ QStringList srcName(sisFile);
+ QStringList dstName("c:\\data\\testtemp.sis");
+ launcher->setCopyFileNames(srcName, dstName);
+ launcher->setInstallFileNames(dstName);
+ }
+ else if (!uploadLocalFile.isEmpty() && uploadInfo.exists()) {
+ launcher->addStartupActions(trk::Launcher::ActionCopy);
+ QStringList srcName(uploadLocalFile);
+ QStringList dstName(QString("c:\\sys\\bin\\") + uploadInfo.fileName());
+ launcher->setCopyFileNames(srcName, dstName);
+ }
+ if (!exeFile.isEmpty()) {
+ launcher->addStartupActions(trk::Launcher::ActionRun);
+ launcher->setFileName(QString("c:\\sys\\bin\\") + info.fileName());
+ launcher->setCommandLineArgs(cmdLine.join(QLatin1String(", ")));
+ }
+ if (!downloadRemoteFile.isEmpty() && !downloadLocalFile.isEmpty()) {
+ launcher->addStartupActions(trk::Launcher::ActionDownload);
+ launcher->setDownloadFileName(downloadRemoteFile, downloadLocalFile);
+ }
+ if (loglevel > 0)
+ outstream << "Connecting to target via " << serialPortName << endl;
+ launcher->setTrkServerName(serialPortName);
+
+ if (loglevel > 1)
+ launcher->setVerbose(1);
+
+ trkHandler.setLogLevel(loglevel);
+ trkHandler.setCrashLogging(crashlog);
+ trkHandler.setCrashLogPath(crashlogpath);
+
+ QObject::connect(launcher.data(), SIGNAL(copyingStarted(const QString &)), &trkHandler, SLOT(copyingStarted(const QString &)));
+ QObject::connect(launcher.data(), SIGNAL(canNotConnect(const QString &)), &trkHandler, SLOT(canNotConnect(const QString &)));
+ QObject::connect(launcher.data(), SIGNAL(canNotCreateFile(const QString &, const QString &)), &trkHandler, SLOT(canNotCreateFile(const QString &, const QString &)));
+ QObject::connect(launcher.data(), SIGNAL(canNotWriteFile(const QString &, const QString &)), &trkHandler, SLOT(canNotWriteFile(const QString &, const QString &)));
+ QObject::connect(launcher.data(), SIGNAL(canNotCloseFile(const QString &, const QString &)), &trkHandler, SLOT(canNotCloseFile(const QString &, const QString &)));
+ QObject::connect(launcher.data(), SIGNAL(installingStarted(const QString &)), &trkHandler, SLOT(installingStarted(const QString &)));
+ QObject::connect(launcher.data(), SIGNAL(canNotInstall(const QString &, const QString &)), &trkHandler, SLOT(canNotInstall(const QString &, const QString &)));
+ QObject::connect(launcher.data(), SIGNAL(installingFinished()), &trkHandler, SLOT(installingFinished()));
+ QObject::connect(launcher.data(), SIGNAL(startingApplication()), &trkHandler, SLOT(startingApplication()));
+ QObject::connect(launcher.data(), SIGNAL(applicationRunning(uint)), &trkHandler, SLOT(applicationRunning(uint)));
+ QObject::connect(launcher.data(), SIGNAL(canNotRun(const QString &)), &trkHandler, SLOT(canNotRun(const QString &)));
+ QObject::connect(launcher.data(), SIGNAL(applicationOutputReceived(const QString &)), &trkHandler, SLOT(applicationOutputReceived(const QString &)));
+ QObject::connect(launcher.data(), SIGNAL(copyProgress(int)), &trkHandler, SLOT(copyProgress(int)));
+ QObject::connect(launcher.data(), SIGNAL(stateChanged(int)), &trkHandler, SLOT(stateChanged(int)));
+ QObject::connect(launcher.data(), SIGNAL(processStopped(uint,uint,uint,QString)), &trkHandler, SLOT(stopped(uint,uint,uint,QString)));
+ QObject::connect(launcher.data(), SIGNAL(libraryLoaded(trk::Library)), &trkHandler, SLOT(libraryLoaded(trk::Library)));
+ QObject::connect(launcher.data(), SIGNAL(libraryUnloaded(trk::Library)), &trkHandler, SLOT(libraryUnloaded(trk::Library)));
+ QObject::connect(launcher.data(), SIGNAL(registersAndCallStackReadComplete(const QList<uint> &,const QByteArray &)), &trkHandler, SLOT(registersAndCallStackReadComplete(const QList<uint> &,const QByteArray &)));
+ QObject::connect(&trkHandler, SIGNAL(resume(uint,uint)), launcher.data(), SLOT(resumeProcess(uint,uint)));
+ QObject::connect(&trkHandler, SIGNAL(terminate()), launcher.data(), SLOT(terminate()));
+ QObject::connect(&trkHandler, SIGNAL(getRegistersAndCallStack(uint,uint)), launcher.data(), SLOT(getRegistersAndCallStack(uint,uint)));
+ QObject::connect(launcher.data(), SIGNAL(finished()), &trkHandler, SLOT(finished()));
+
+ QObject::connect(OsSignalConverter::instance(), SIGNAL(terminate()), launcher.data(), SLOT(terminate()), Qt::QueuedConnection);
+
+ QTimer timer;
+ timer.setSingleShot(true);
+ QObject::connect(&timer, SIGNAL(timeout()), &trkHandler, SLOT(timeout()));
+ if (timeout > 0) {
+ timer.start(timeout);
+ }
+
+ QString errorMessage;
+ if (!launcher->startServer(&errorMessage)) {
+ errstream << errorMessage << endl;
+ return 1;
+ }
}
return a.exec();
diff --git a/tools/runonphone/runonphone.pro b/tools/runonphone/runonphone.pro
index 7ff361c..d006a05 100644
--- a/tools/runonphone/runonphone.pro
+++ b/tools/runonphone/runonphone.pro
@@ -1,27 +1,29 @@
TEMPLATE = app
QT -= gui
-CONFIG += console
+CONFIG += console static
CONFIG -= app_bundle
include(symbianutils/symbianutils.pri)
SOURCES += main.cpp \
trksignalhandler.cpp \
- ossignalconverter.cpp
+ ossignalconverter.cpp \
+ codasignalhandler.cpp
HEADERS += trksignalhandler.h \
serenum.h \
ossignalconverter.h \
- ossignalconverter_p.h
+ ossignalconverter_p.h \
+ codasignalhandler.h
DEFINES += SYMBIANUTILS_INCLUDE_PRI
windows {
SOURCES += serenum_win.cpp
LIBS += -lsetupapi \
- -luuid \
- -ladvapi32
+ -luuid \
+ -ladvapi32
}
else:unix:!symbian {
SOURCES += serenum_unix.cpp
diff --git a/tools/runonphone/symbianutils/codadevice.cpp b/tools/runonphone/symbianutils/codadevice.cpp
new file mode 100644
index 0000000..8932800
--- /dev/null
+++ b/tools/runonphone/symbianutils/codadevice.cpp
@@ -0,0 +1,1501 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the tools applications of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, 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.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "codadevice.h"
+#include "json.h"
+#include "trkutils.h"
+
+#include <QtNetwork/QAbstractSocket>
+#include <QtCore/QDebug>
+#include <QtCore/QVector>
+#include <QtCore/QQueue>
+#include <QtCore/QTextStream>
+#include <QtCore/QDateTime>
+#include <QtCore/QFileInfo>
+
+enum { debug = 0 };
+
+static const char tcpMessageTerminatorC[] = "\003\001";
+
+// Serial Ping: 0xfc,0x1f
+static const char serialPingC[] = "\xfc\x1f";
+// Serial Pong: 0xfc,0xf1, followed by version info
+static const char serialPongC[] = "\xfc\xf1";
+
+static const char locatorAnswerC[] = "E\0Locator\0Hello\0[\"Locator\"]";
+
+/* Serial messages > (1K - 2) have to chunked in order to pass the USB
+ * router as '0xfe char(chunkCount - 1) data' ... '0x0 char(chunkCount - 2) data'
+ * ... '0x0 0x0 last-data' */
+static const unsigned serialChunkLength = 0x400; // 1K max USB router
+static const int maxSerialMessageLength = 0x10000; // given chunking scheme
+
+static const char validProtocolIdStart = (char)0x90;
+static const char validProtocolIdEnd = (char)0x95;
+static const char codaProtocolId = (char)0x92;
+static const unsigned char serialChunkingStart = 0xfe;
+static const unsigned char serialChunkingContinuation = 0x0;
+enum { SerialChunkHeaderSize = 2 };
+
+// Create USB router frame
+static inline void encodeSerialFrame(const QByteArray &data, QByteArray *target, char protocolId)
+{
+ target->append(char(0x01));
+ target->append(protocolId);
+ appendShort(target, ushort(data.size()), trk::BigEndian);
+ target->append(data);
+}
+
+// Split in chunks of 1K according to CODA protocol chunking
+static inline QByteArray encodeUsbSerialMessage(const QByteArray &dataIn)
+{
+ // Reserve 2 header bytes
+ static const int chunkSize = serialChunkLength - SerialChunkHeaderSize;
+ const int size = dataIn.size();
+ QByteArray frame;
+ // Do we need to split?
+ if (size < chunkSize) { // Nope, all happy.
+ frame.reserve(size + 4);
+ encodeSerialFrame(dataIn, &frame, codaProtocolId);
+ return frame;
+ }
+ // Split.
+ unsigned chunkCount = size / chunkSize;
+ if (size % chunkSize)
+ chunkCount++;
+ if (debug)
+ qDebug("Serial: Splitting message of %d bytes into %u chunks of %d", size, chunkCount, chunkSize);
+
+ frame.reserve((4 + serialChunkLength) * chunkCount);
+ int pos = 0;
+ for (unsigned c = chunkCount - 1; pos < size ; c--) {
+ QByteArray chunk; // chunk with long message start/continuation code
+ chunk.reserve(serialChunkLength);
+ chunk.append(pos ? serialChunkingContinuation : serialChunkingStart);
+ chunk.append(char(static_cast<unsigned char>(c))); // Avoid any signedness issues.
+ const int chunkEnd = qMin(pos + chunkSize, size);
+ chunk.append(dataIn.mid(pos, chunkEnd - pos));
+ encodeSerialFrame(chunk, &frame, codaProtocolId);
+ pos = chunkEnd;
+ }
+ if (debug > 1)
+ qDebug("Serial chunked:\n%s", qPrintable(Coda::formatData(frame)));
+ return frame;
+}
+
+namespace Coda {
+// ------------- CodaCommandError
+
+CodaCommandError::CodaCommandError() : timeMS(0), code(0), alternativeCode(0)
+{
+}
+
+void CodaCommandError::clear()
+{
+ timeMS = 0;
+ code = alternativeCode = 0;
+ format.clear();
+ alternativeOrganization.clear();
+}
+
+QDateTime CodaCommandResult::tcfTimeToQDateTime(quint64 tcfTimeMS)
+{
+ const QDateTime time(QDate(1970, 1, 1));
+ return time.addMSecs(tcfTimeMS);
+}
+
+void CodaCommandError::write(QTextStream &str) const
+{
+ if (isError()) {
+ if (debug && timeMS)
+ str << CodaCommandResult::tcfTimeToQDateTime(timeMS).toString(Qt::ISODate) << ": ";
+ str << "'" << format << '\'' //for symbian the format is the real error message
+ << " Code: " << code;
+ if (!alternativeOrganization.isEmpty())
+ str << " ('" << alternativeOrganization << "', code: " << alternativeCode << ')';
+ } else{
+ str << "<No error>";
+ }
+}
+
+QString CodaCommandError::toString() const
+{
+ QString rc;
+ QTextStream str(&rc);
+ write(str);
+ return rc;
+}
+
+bool CodaCommandError::isError() const
+{
+ return timeMS != 0 || code != 0 || !format.isEmpty() || alternativeCode != 0;
+}
+
+/* {"Time":1277459762255,"Code":1,"AltCode":-6,"AltOrg":"POSIX","Format":"Unknown error: -6"} */
+bool CodaCommandError::parse(const QVector<JsonValue> &values)
+{
+ // Parse an arbitrary hash (that could as well be a command response)
+ // and check for error elements. It looks like sometimes errors are appended
+ // to other values.
+ unsigned errorKeyCount = 0;
+ clear();
+ do {
+ if (values.isEmpty())
+ break;
+ // Errors are mostly appended, except for FileSystem::open, in which case
+ // a string "null" file handle (sic!) follows the error.
+ const int last = values.size() - 1;
+ const int checkIndex = last == 1 && values.at(last).data() == "null" ?
+ last - 1 : last;
+ if (values.at(checkIndex).type() != JsonValue::Object)
+ break;
+ foreach (const JsonValue &c, values.at(checkIndex).children()) {
+ if (c.name() == "Time") {
+ timeMS = c.data().toULongLong();
+ errorKeyCount++;
+ } else if (c.name() == "Code") {
+ code = c.data().toLongLong();
+ errorKeyCount++;
+ } else if (c.name() == "Format") {
+ format = c.data();
+ errorKeyCount++;
+ } else if (c.name() == "AltCode") {
+ alternativeCode = c.data().toULongLong();
+ errorKeyCount++;
+ } else if (c.name() == "AltOrg") {
+ alternativeOrganization = c.data();
+ errorKeyCount++;
+ }
+ }
+ } while (false);
+ const bool errorFound = errorKeyCount >= 2u; // Should be at least 'Time', 'Code'.
+ if (!errorFound)
+ clear();
+ if (debug) {
+ qDebug("CodaCommandError::parse: Found error %d (%u): ", errorFound, errorKeyCount);
+ if (!values.isEmpty())
+ qDebug() << values.back().toString();
+ }
+ return errorFound;
+}
+
+// ------------ CodaCommandResult
+
+CodaCommandResult::CodaCommandResult(Type t) :
+ type(t), service(LocatorService)
+{
+}
+
+CodaCommandResult::CodaCommandResult(char typeChar, Services s,
+ const QByteArray &r,
+ const QVector<JsonValue> &v,
+ const QVariant &ck) :
+ type(FailReply), service(s), request(r), values(v), cookie(ck)
+{
+ switch (typeChar) {
+ case 'N':
+ type = FailReply;
+ break;
+ case 'P':
+ type = ProgressReply;
+ break;
+ case 'R':
+ type = commandError.parse(values) ? CommandErrorReply : SuccessReply;
+ break;
+ default:
+ qWarning("Unknown TCF reply type '%c'", typeChar);
+ }
+}
+
+QString CodaCommandResult::errorString() const
+{
+ QString rc;
+ QTextStream str(&rc);
+
+ switch (type) {
+ case SuccessReply:
+ case ProgressReply:
+ str << "<No error>";
+ return rc;
+ case FailReply:
+ str << "NAK";
+ return rc;
+ case CommandErrorReply:
+ commandError.write(str);
+ break;
+ }
+ if (debug) {
+ // Append the failed command for reference
+ str << " (Command was: '";
+ QByteArray printableRequest = request;
+ printableRequest.replace('\0', '|');
+ str << printableRequest << "')";
+ }
+ return rc;
+}
+
+QString CodaCommandResult::toString() const
+{
+ QString rc;
+ QTextStream str(&rc);
+ str << "Command answer ";
+ switch (type) {
+ case SuccessReply:
+ str << "[success]";
+ break;
+ case CommandErrorReply:
+ str << "[command error]";
+ break;
+ case FailReply:
+ str << "[fail (NAK)]";
+ break;
+ case ProgressReply:
+ str << "[progress]";
+ break;
+ }
+ str << ", " << values.size() << " values(s) to request: '";
+ QByteArray printableRequest = request;
+ printableRequest.replace('\0', '|');
+ str << printableRequest << "' ";
+ if (cookie.isValid())
+ str << " cookie: " << cookie.toString();
+ str << '\n';
+ for (int i = 0, count = values.size(); i < count; i++)
+ str << '#' << i << ' ' << values.at(i).toString() << '\n';
+ if (type == CommandErrorReply)
+ str << "Error: " << errorString();
+ return rc;
+}
+
+CodaStatResponse::CodaStatResponse() : size(0)
+{
+}
+
+struct CodaSendQueueEntry
+{
+ typedef CodaDevice::MessageType MessageType;
+
+ explicit CodaSendQueueEntry(MessageType mt,
+ int tok,
+ Services s,
+ const QByteArray &d,
+ const CodaCallback &cb= CodaCallback(),
+ const QVariant &ck = QVariant()) :
+ messageType(mt), service(s), data(d), token(tok), cookie(ck), callback(cb) {}
+
+ MessageType messageType;
+ Services service;
+ QByteArray data;
+ int token;
+ QVariant cookie;
+ CodaCallback callback;
+ unsigned specialHandling;
+};
+
+struct CodaDevicePrivate {
+ typedef CodaDevice::IODevicePtr IODevicePtr;
+ typedef QHash<int, CodaSendQueueEntry> TokenWrittenMessageMap;
+
+ CodaDevicePrivate();
+
+ const QByteArray m_tcpMessageTerminator;
+
+ IODevicePtr m_device;
+ unsigned m_verbose;
+ QByteArray m_readBuffer;
+ QByteArray m_serialBuffer; // for chunked messages
+ int m_token;
+ QQueue<CodaSendQueueEntry> m_sendQueue;
+ TokenWrittenMessageMap m_writtenMessages;
+ QVector<QByteArray> m_registerNames;
+ QVector<QByteArray> m_fakeGetMRegisterValues;
+ bool m_serialFrame;
+ bool m_serialPingOnly;
+};
+
+CodaDevicePrivate::CodaDevicePrivate() :
+ m_tcpMessageTerminator(tcpMessageTerminatorC),
+ m_verbose(0), m_token(0), m_serialFrame(false), m_serialPingOnly(false)
+{
+}
+
+CodaDevice::CodaDevice(QObject *parent) :
+ QObject(parent), d(new CodaDevicePrivate)
+{
+ if (debug) setVerbose(true);
+}
+
+CodaDevice::~CodaDevice()
+{
+ delete d;
+}
+
+QVector<QByteArray> CodaDevice::registerNames() const
+{
+ return d->m_registerNames;
+}
+
+void CodaDevice::setRegisterNames(const QVector<QByteArray>& n)
+{
+ d->m_registerNames = n;
+ if (d->m_verbose) {
+ QString msg;
+ QTextStream str(&msg);
+ const int count = n.size();
+ str << "Registers (" << count << "): ";
+ for (int i = 0; i < count; i++)
+ str << '#' << i << '=' << n.at(i) << ' ';
+ emitLogMessage(msg);
+ }
+}
+
+CodaDevice::IODevicePtr CodaDevice::device() const
+{
+ return d->m_device;
+}
+
+CodaDevice::IODevicePtr CodaDevice::takeDevice()
+{
+ const IODevicePtr old = d->m_device;
+ if (!old.isNull()) {
+ old.data()->disconnect(this);
+ d->m_device = IODevicePtr();
+ }
+ d->m_readBuffer.clear();
+ d->m_token = 0;
+ d->m_sendQueue.clear();
+ return old;
+}
+
+void CodaDevice::setDevice(const IODevicePtr &dp)
+{
+ if (dp.data() == d->m_device.data())
+ return;
+ if (dp.isNull()) {
+ emitLogMessage(QLatin1String("Internal error: Attempt to set NULL device."));
+ return;
+ }
+ takeDevice();
+ d->m_device = dp;
+ connect(dp.data(), SIGNAL(readyRead()), this, SLOT(slotDeviceReadyRead()));
+ if (QAbstractSocket *s = qobject_cast<QAbstractSocket *>(dp.data())) {
+ connect(s, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(slotDeviceError()));
+ connect(s, SIGNAL(stateChanged(QAbstractSocket::SocketState)), this, SLOT(slotDeviceSocketStateChanged()));
+ }
+}
+
+void CodaDevice::slotDeviceError()
+{
+ const QString message = d->m_device->errorString();
+ emitLogMessage(message);
+ emit error(message);
+}
+
+void CodaDevice::slotDeviceSocketStateChanged()
+{
+ if (const QAbstractSocket *s = qobject_cast<const QAbstractSocket *>(d->m_device.data())) {
+ const QAbstractSocket::SocketState st = s->state();
+ switch (st) {
+ case QAbstractSocket::UnconnectedState:
+ emitLogMessage(QLatin1String("Unconnected"));
+ break;
+ case QAbstractSocket::HostLookupState:
+ emitLogMessage(QLatin1String("HostLookupState"));
+ break;
+ case QAbstractSocket::ConnectingState:
+ emitLogMessage(QLatin1String("Connecting"));
+ break;
+ case QAbstractSocket::ConnectedState:
+ emitLogMessage(QLatin1String("Connected"));
+ break;
+ case QAbstractSocket::ClosingState:
+ emitLogMessage(QLatin1String("Closing"));
+ break;
+ default:
+ emitLogMessage(QString::fromLatin1("State %1").arg(st));
+ break;
+ }
+ }
+}
+
+static inline QString debugMessage(QByteArray message, const char *prefix = 0)
+{
+ const bool isBinary = !message.isEmpty() && message.at(0) < 0;
+ if (isBinary) {
+ message = message.toHex(); // Some serial special message
+ } else {
+ message.replace('\0', '|');
+ }
+ const QString messageS = QString::fromLatin1(message);
+ return prefix ?
+ (QLatin1String(prefix) + messageS) : messageS;
+}
+
+void CodaDevice::slotDeviceReadyRead()
+{
+ const QByteArray newData = d->m_device->readAll();
+ d->m_readBuffer += newData;
+ if (debug)
+ qDebug("ReadBuffer: %s", qPrintable(trk::stringFromArray(newData)));
+ if (d->m_serialFrame) {
+ deviceReadyReadSerial();
+ } else {
+ deviceReadyReadTcp();
+ }
+}
+
+// Find a serial header in input stream '0x1', '0x92', 'lenH', 'lenL'
+// and return message position and size.
+QPair<int, int> CodaDevice::findSerialHeader(QByteArray &in)
+{
+ static const char header1 = 0x1;
+ // Header should in theory always be at beginning of
+ // buffer. Warn if there are bogus data in-between.
+
+ while (in.size() >= 4) {
+ if (in.at(0) == header1 && in.at(1) == codaProtocolId) {
+ // Good packet
+ const int length = trk::extractShort(in.constData() + 2);
+ return QPair<int, int>(4, length);
+ } else if (in.at(0) == header1 && in.at(1) >= validProtocolIdStart && in.at(1) <= validProtocolIdEnd) {
+ // We recognise it but it's not a TCF message - emit it for any interested party to handle
+ const int length = trk::extractShort(in.constData() + 2);
+ if (4 + length <= in.size()) {
+ // We have all the data
+ QByteArray data(in.mid(4, length));
+ emit unknownEvent(in.at(1), data);
+ in.remove(0, 4+length);
+ // and continue
+ } else {
+ // If we don't have all this packet, there can't be any data following it, so return now
+ // and wait for more data
+ return QPair<int, int>(-1, -1);
+ }
+ } else {
+ // Bad data - log it, remove it, and go round again
+ int nextHeader = in.indexOf(header1, 1);
+ QByteArray bad = in.mid(0, nextHeader);
+ qWarning("Bogus data received on serial line: %s\n"
+ "Frame Header at: %d", qPrintable(trk::stringFromArray(bad)), nextHeader);
+ in.remove(0, bad.length());
+ // and continue
+ }
+ }
+ return QPair<int, int>(-1, -1); // No more data, or not enough for a complete header
+}
+
+void CodaDevice::deviceReadyReadSerial()
+{
+ do {
+ // Extract message (pos,len)
+ const QPair<int, int> messagePos = findSerialHeader(d->m_readBuffer);
+ if (messagePos.first < 0)
+ break;
+ // Do we have the complete message?
+ const int messageEnd = messagePos.first + messagePos.second;
+ if (messageEnd > d->m_readBuffer.size())
+ break;
+ processSerialMessage(d->m_readBuffer.mid(messagePos.first, messagePos.second));
+ d->m_readBuffer.remove(0, messageEnd);
+ } while (!d->m_readBuffer.isEmpty());
+ checkSendQueue(); // Send off further messages
+}
+
+void CodaDevice::processSerialMessage(const QByteArray &message)
+{
+ if (debug > 1)
+ qDebug("Serial message: %s",qPrintable(trk::stringFromArray(message)));
+ if (message.isEmpty())
+ return;
+ // Is thing a ping/pong response
+ const int size = message.size();
+ if (message.startsWith(serialPongC)) {
+ const QString version = QString::fromLatin1(message.mid(sizeof(serialPongC) - 1));
+ emitLogMessage(QString::fromLatin1("Serial connection from '%1'").arg(version));
+ emit serialPong(version);
+ // Answer with locator.
+ if (!d->m_serialPingOnly)
+ writeMessage(QByteArray(locatorAnswerC, sizeof(locatorAnswerC)));
+ return;
+ }
+ // Check for long message (see top, '0xfe #number, data' or '0x0 #number, data')
+ // TODO: This is currently untested.
+ const unsigned char *dataU = reinterpret_cast<const unsigned char *>(message.constData());
+ const bool isLongMessageStart = size > SerialChunkHeaderSize
+ && *dataU == serialChunkingStart;
+ const bool isLongMessageContinuation = size > SerialChunkHeaderSize
+ && *dataU == serialChunkingContinuation;
+ if (isLongMessageStart || isLongMessageContinuation) {
+ const unsigned chunkNumber = *++dataU;
+ if (isLongMessageStart) { // Start new buffer
+ d->m_serialBuffer.clear();
+ d->m_serialBuffer.reserve( (chunkNumber + 1) * serialChunkLength);
+ }
+ d->m_serialBuffer.append(message.mid(SerialChunkHeaderSize, size - SerialChunkHeaderSize));
+ // Last chunk? - Process
+ if (!chunkNumber) {
+ processMessage(d->m_serialBuffer);
+ d->m_serialBuffer.clear();
+ d->m_serialBuffer.squeeze();
+ }
+ } else {
+ processMessage(message); // Normal, unchunked message
+ }
+}
+
+void CodaDevice::deviceReadyReadTcp()
+{
+ // Take complete message off front of readbuffer.
+ do {
+ const int messageEndPos = d->m_readBuffer.indexOf(d->m_tcpMessageTerminator);
+ if (messageEndPos == -1)
+ break;
+ if (messageEndPos == 0) {
+ // TCF TRK 4.0.5 emits empty messages on errors.
+ emitLogMessage(QString::fromLatin1("An empty TCF TRK message has been received."));
+ } else {
+ processMessage(d->m_readBuffer.left(messageEndPos));
+ }
+ d->m_readBuffer.remove(0, messageEndPos + d->m_tcpMessageTerminator.size());
+ } while (!d->m_readBuffer.isEmpty());
+ checkSendQueue(); // Send off further messages
+}
+
+void CodaDevice::processMessage(const QByteArray &message)
+{
+ if (debug)
+ qDebug("Read %d bytes:\n%s", message.size(), qPrintable(formatData(message)));
+ if (const int errorCode = parseMessage(message)) {
+ emitLogMessage(QString::fromLatin1("Parse error %1 : %2").
+ arg(errorCode).arg(debugMessage(message)));
+ if (debug)
+ qDebug("Parse error %d for %d bytes:\n%s", errorCode,
+ message.size(), qPrintable(formatData(message)));
+ }
+}
+
+// Split \0-terminated message into tokens, skipping the initial type character
+static inline QVector<QByteArray> splitMessage(const QByteArray &message)
+{
+ QVector<QByteArray> tokens;
+ tokens.reserve(7);
+ const int messageSize = message.size();
+ for (int pos = 2; pos < messageSize; ) {
+ const int nextPos = message.indexOf('\0', pos);
+ if (nextPos == -1)
+ break;
+ tokens.push_back(message.mid(pos, nextPos - pos));
+ pos = nextPos + 1;
+ }
+ return tokens;
+}
+
+int CodaDevice::parseMessage(const QByteArray &message)
+{
+ if (d->m_verbose)
+ emitLogMessage(debugMessage(message, "TCF ->"));
+ // Special JSON parse error message or protocol format error.
+ // The port is usually closed after receiving it.
+ // "\3\2{"Time":1276096098255,"Code":3,"Format": "Protocol format error"}"
+ if (message.startsWith("\003\002")) {
+ QByteArray text = message.mid(2);
+ const QString errorMessage = QString::fromLatin1("Parse error received: %1").arg(QString::fromAscii(text));
+ emit error(errorMessage);
+ return 0;
+ }
+ if (message.size() < 4 || message.at(1) != '\0')
+ return 1;
+ // Split into tokens
+ const char type = message.at(0);
+ const QVector<QByteArray> tokens = splitMessage(message);
+ switch (type) {
+ case 'E':
+ return parseTcfEvent(tokens);
+ case 'R': // Command replies
+ case 'N':
+ case 'P':
+ return parseTcfCommandReply(type, tokens);
+ default:
+ emitLogMessage(QString::fromLatin1("Unhandled message type: %1").arg(debugMessage(message)));
+ return 756;
+ }
+ return 0;
+}
+
+int CodaDevice::parseTcfCommandReply(char type, const QVector<QByteArray> &tokens)
+{
+ typedef CodaDevicePrivate::TokenWrittenMessageMap::iterator TokenWrittenMessageMapIterator;
+ // Find the corresponding entry in the written messages hash.
+ const int tokenCount = tokens.size();
+ if (tokenCount < 1)
+ return 234;
+ bool tokenOk;
+ const int token = tokens.at(0).toInt(&tokenOk);
+ if (!tokenOk)
+ return 235;
+ const TokenWrittenMessageMapIterator it = d->m_writtenMessages.find(token);
+ if (it == d->m_writtenMessages.end()) {
+ qWarning("CodaDevice: Internal error: token %d not found for '%s'",
+ token, qPrintable(joinByteArrays(tokens)));
+ return 236;
+ }
+ // No callback: remove entry from map, happy
+ const unsigned specialHandling = it.value().specialHandling;
+ if (!it.value().callback && specialHandling == 0u) {
+ d->m_writtenMessages.erase(it);
+ return 0;
+ }
+ // Parse values into JSON
+ QVector<JsonValue> values;
+ values.reserve(tokenCount);
+ for (int i = 1; i < tokenCount; i++) {
+ if (!tokens.at(i).isEmpty()) { // Strange: Empty tokens occur.
+ const JsonValue value(tokens.at(i));
+ if (value.isValid()) {
+ values.push_back(value);
+ } else {
+ qWarning("JSON parse error for reply to command token %d: #%d '%s'",
+ token, i, tokens.at(i).constData());
+ d->m_writtenMessages.erase(it);
+ return -1;
+ }
+ }
+ }
+ // Construct result and invoke callback, remove entry from map.
+ CodaCommandResult result(type, it.value().service, it.value().data,
+ values, it.value().cookie);
+
+ if (it.value().callback)
+ it.value().callback(result);
+ d->m_writtenMessages.erase(it);
+ return 0;
+}
+
+int CodaDevice::parseTcfEvent(const QVector<QByteArray> &tokens)
+{
+ // Event: Ignore the periodical heartbeat event, answer 'Hello',
+ // emit signal for the rest
+ if (tokens.size() < 3)
+ return 433;
+ const Services service = serviceFromName(tokens.at(0).constData());
+ if (service == LocatorService && tokens.at(1) == "peerHeartBeat")
+ return 0;
+ QVector<JsonValue> values;
+ for (int i = 2; i < tokens.size(); i++) {
+ const JsonValue value(tokens.at(i));
+ if (!value.isValid())
+ return 434;
+ values.push_back(value);
+ }
+ // Parse known events, emit signals
+ QScopedPointer<CodaEvent> knownEvent(CodaEvent::parseEvent(service, tokens.at(1), values));
+ if (!knownEvent.isNull()) {
+ // Answer hello event (WLAN)
+ if (knownEvent->type() == CodaEvent::LocatorHello)
+ if (!d->m_serialFrame)
+ writeMessage(QByteArray(locatorAnswerC, sizeof(locatorAnswerC)));
+ emit tcfEvent(*knownEvent);
+ }
+ emit genericTcfEvent(service, tokens.at(1), values);
+
+ if (debug || d->m_verbose) {
+ QString msg;
+ QTextStream str(&msg);
+ if (knownEvent.isNull()) {
+ str << "Event: " << tokens.at(0) << ' ' << tokens.at(1) << '\n';
+ foreach(const JsonValue &val, values)
+ str << " " << val.toString() << '\n';
+ } else {
+ str << knownEvent->toString();
+ }
+ emitLogMessage(msg);
+ }
+
+ return 0;
+}
+
+unsigned CodaDevice::verbose() const
+{
+ return d->m_verbose;
+}
+
+bool CodaDevice::serialFrame() const
+{
+ return d->m_serialFrame;
+}
+
+void CodaDevice::setSerialFrame(bool s)
+{
+ d->m_serialFrame = s;
+}
+
+void CodaDevice::setVerbose(unsigned v)
+{
+ d->m_verbose = v;
+}
+
+void CodaDevice::emitLogMessage(const QString &m)
+{
+ if (debug)
+ qWarning("%s", qPrintable(m));
+ emit logMessage(m);
+}
+
+bool CodaDevice::checkOpen()
+{
+ if (d->m_device.isNull()) {
+ emitLogMessage(QLatin1String("Internal error: No device set on CodaDevice."));
+ return false;
+ }
+ if (!d->m_device->isOpen()) {
+ emitLogMessage(QLatin1String("Internal error: Device not open in CodaDevice."));
+ return false;
+ }
+ return true;
+}
+
+void CodaDevice::sendSerialPing(bool pingOnly)
+{
+ if (!checkOpen())
+ return;
+
+ d->m_serialPingOnly = pingOnly;
+ setSerialFrame(true);
+ writeMessage(QByteArray(serialPingC, qstrlen(serialPingC)), false);
+ if (d->m_verbose)
+ emitLogMessage(QLatin1String("Ping..."));
+}
+
+void CodaDevice::sendCodaMessage(MessageType mt, Services service, const char *command,
+ const char *commandParameters, // may contain '\0'
+ int commandParametersLength,
+ const CodaCallback &callBack,
+ const QVariant &cookie)
+
+{
+ if (!checkOpen())
+ return;
+ // Format the message
+ const int token = d->m_token++;
+ QByteArray data;
+ data.reserve(30 + commandParametersLength);
+ data.append('C');
+ data.append('\0');
+ data.append(QByteArray::number(token));
+ data.append('\0');
+ data.append(serviceName(service));
+ data.append('\0');
+ data.append(command);
+ data.append('\0');
+ if (commandParametersLength)
+ data.append(commandParameters, commandParametersLength);
+ const CodaSendQueueEntry entry(mt, token, service, data, callBack, cookie);
+ d->m_sendQueue.enqueue(entry);
+ checkSendQueue();
+}
+
+void CodaDevice::sendCodaMessage(MessageType mt, Services service, const char *command,
+ const QByteArray &commandParameters,
+ const CodaCallback &callBack,
+ const QVariant &cookie)
+{
+ sendCodaMessage(mt, service, command, commandParameters.constData(), commandParameters.size(),
+ callBack, cookie);
+}
+
+// Enclose in message frame and write.
+void CodaDevice::writeMessage(QByteArray data, bool ensureTerminating0)
+{
+ if (!checkOpen())
+ return;
+
+ if (d->m_serialFrame && data.size() > maxSerialMessageLength) {
+ qCritical("Attempt to send large message (%d bytes) exceeding the "
+ "limit of %d bytes over serial channel. Skipping.",
+ data.size(), maxSerialMessageLength);
+ return;
+ }
+
+ if (d->m_verbose)
+ emitLogMessage(debugMessage(data, "TCF <-"));
+
+ // Ensure \0-termination which easily gets lost in QByteArray CT.
+ if (ensureTerminating0 && !data.endsWith('\0'))
+ data.append('\0');
+ if (d->m_serialFrame) {
+ data = encodeUsbSerialMessage(data);
+ } else {
+ data += d->m_tcpMessageTerminator;
+ }
+
+ if (debug > 1)
+ qDebug("Writing:\n%s", qPrintable(formatData(data)));
+
+ int result = d->m_device->write(data);
+ if (result < data.length())
+ qWarning("Failed to write all data! result=%d", result);
+ if (QAbstractSocket *as = qobject_cast<QAbstractSocket *>(d->m_device.data()))
+ as->flush();
+}
+
+void CodaDevice::writeCustomData(char protocolId, const QByteArray &data)
+{
+ if (!checkOpen())
+ return;
+
+ if (!d->m_serialFrame) {
+ qWarning("Ignoring request to send data to non-serial CodaDevice");
+ return;
+ }
+ if (data.length() > 0xFFFF) {
+ qWarning("Ignoring request to send too large packet, of size %d", data.length());
+ return;
+ }
+ QByteArray framedData;
+ encodeSerialFrame(data, &framedData, protocolId);
+ device()->write(framedData);
+}
+
+void CodaDevice::checkSendQueue()
+{
+ // Fire off messages or invoke noops until a message with reply is found
+ // and an entry to writtenMessages is made.
+ while (d->m_writtenMessages.empty()) {
+ if (d->m_sendQueue.isEmpty())
+ break;
+ CodaSendQueueEntry entry = d->m_sendQueue.dequeue();
+ switch (entry.messageType) {
+ case MessageWithReply:
+ d->m_writtenMessages.insert(entry.token, entry);
+ writeMessage(entry.data);
+ break;
+ case MessageWithoutReply:
+ writeMessage(entry.data);
+ break;
+ case NoopMessage: // Invoke the noop-callback for synchronization
+ if (entry.callback) {
+ CodaCommandResult noopResult(CodaCommandResult::SuccessReply);
+ noopResult.cookie = entry.cookie;
+ entry.callback(noopResult);
+ }
+ break;
+ }
+ }
+}
+
+// Fix slashes
+static inline QString fixFileName(QString in)
+{
+ in.replace(QLatin1Char('/'), QLatin1Char('\\'));
+ return in;
+}
+
+// Start a process (consisting of a non-reply setSettings and start).
+void CodaDevice::sendProcessStartCommand(const CodaCallback &callBack,
+ const QString &binaryIn,
+ unsigned uid,
+ QStringList arguments,
+ QString workingDirectory,
+ bool debugControl,
+ const QStringList &additionalLibraries,
+ const QVariant &cookie)
+{
+ // Obtain the bin directory, expand by c:/sys/bin if missing
+ const QChar backSlash('\\');
+ int slashPos = binaryIn.lastIndexOf(QLatin1Char('/'));
+ if (slashPos == -1)
+ slashPos = binaryIn.lastIndexOf(backSlash);
+ const QString sysBin = QLatin1String("c:/sys/bin");
+ const QString binaryFileName = slashPos == -1 ? binaryIn : binaryIn.mid(slashPos + 1);
+
+ if (workingDirectory.isEmpty())
+ workingDirectory = sysBin;
+
+ // Format settings with empty dummy parameter
+ QByteArray setData;
+ JsonInputStream setStr(setData);
+ setStr << "" << '\0'
+ << '[' << "exeToLaunch" << ',' << "addExecutables" << ',' << "addLibraries" << ',' << "logUserTraces" << ']'
+ << '\0' << '['
+ << binaryFileName << ','
+ << '{' << binaryFileName << ':' << QString::number(uid, 16) << '}' << ','
+ << additionalLibraries << ',' << true
+ << ']';
+ sendCodaMessage(
+#if 1
+ MessageWithReply, // TCF TRK 4.0.5 onwards
+#else
+ MessageWithoutReply, // TCF TRK 4.0.2
+#endif
+ SettingsService, "set", setData);
+
+ QByteArray startData;
+ JsonInputStream startStr(startData);
+ startStr << "" //We don't really know the drive of the working dir
+ << '\0' << binaryFileName << '\0' << arguments << '\0'
+ << QStringList() << '\0' // Env is an array ["PATH=value"] (non-standard)
+ << debugControl;
+ sendCodaMessage(MessageWithReply, ProcessesService, "start", startData, callBack, cookie);
+}
+
+void CodaDevice::sendRunProcessCommand(const CodaCallback &callBack,
+ const QString &processName,
+ QStringList arguments,
+ const QVariant &cookie)
+{
+ QByteArray startData;
+ JsonInputStream startStr(startData);
+ startStr << "" //We don't really know the drive of the working dir
+ << '\0' << processName << '\0' << arguments << '\0'
+ << QStringList() << '\0' // Env is an array ["PATH=value"] (non-standard)
+ << false; // Don't attach debugger
+ sendCodaMessage(MessageWithReply, ProcessesService, "start", startData, callBack, cookie);
+}
+
+void CodaDevice::sendSettingsEnableLogCommand()
+{
+
+ QByteArray setData;
+ JsonInputStream setStr(setData);
+ setStr << "" << '\0'
+ << '[' << "logUserTraces" << ']'
+ << '\0' << '['
+ << true
+ << ']';
+ sendCodaMessage(
+#if 1
+ MessageWithReply, // TCF TRK 4.0.5 onwards
+#else
+ MessageWithoutReply, // TCF TRK 4.0.2
+#endif
+ SettingsService, "set", setData);
+}
+
+void CodaDevice::sendProcessTerminateCommand(const CodaCallback &callBack,
+ const QByteArray &id,
+ const QVariant &cookie)
+{
+ QByteArray data;
+ JsonInputStream str(data);
+ str << id;
+ sendCodaMessage(MessageWithReply, ProcessesService, "terminate", data, callBack, cookie);
+}
+
+void CodaDevice::sendRunControlTerminateCommand(const CodaCallback &callBack,
+ const QByteArray &id,
+ const QVariant &cookie)
+{
+ QByteArray data;
+ JsonInputStream str(data);
+ str << id;
+ sendCodaMessage(MessageWithReply, RunControlService, "terminate", data, callBack, cookie);
+}
+
+// Non-standard: Remove executable from settings
+void CodaDevice::sendSettingsRemoveExecutableCommand(const QString &binaryIn,
+ unsigned uid,
+ const QStringList &additionalLibraries,
+ const QVariant &cookie)
+{
+ QByteArray setData;
+ JsonInputStream setStr(setData);
+ setStr << "" << '\0'
+ << '[' << "removedExecutables" << ',' << "removedLibraries" << ']'
+ << '\0' << '['
+ << '{' << QFileInfo(binaryIn).fileName() << ':' << QString::number(uid, 16) << '}' << ','
+ << additionalLibraries
+ << ']';
+ sendCodaMessage(MessageWithoutReply, SettingsService, "set", setData, CodaCallback(), cookie);
+}
+
+void CodaDevice::sendRunControlResumeCommand(const CodaCallback &callBack,
+ const QByteArray &id,
+ RunControlResumeMode mode,
+ unsigned count,
+ quint64 rangeStart,
+ quint64 rangeEnd,
+ const QVariant &cookie)
+{
+ QByteArray resumeData;
+ JsonInputStream str(resumeData);
+ str << id << '\0' << int(mode) << '\0' << count;
+ switch (mode) {
+ case RM_STEP_OVER_RANGE:
+ case RM_STEP_INTO_RANGE:
+ case RM_REVERSE_STEP_OVER_RANGE:
+ case RM_REVERSE_STEP_INTO_RANGE:
+ str << '\0' << '{' << "RANGE_START" << ':' << rangeStart
+ << ',' << "RANGE_END" << ':' << rangeEnd << '}';
+ break;
+ default:
+ break;
+ }
+ sendCodaMessage(MessageWithReply, RunControlService, "resume", resumeData, callBack, cookie);
+}
+
+void CodaDevice::sendRunControlSuspendCommand(const CodaCallback &callBack,
+ const QByteArray &id,
+ const QVariant &cookie)
+{
+ QByteArray data;
+ JsonInputStream str(data);
+ str << id;
+ sendCodaMessage(MessageWithReply, RunControlService, "suspend", data, callBack, cookie);
+}
+
+void CodaDevice::sendRunControlResumeCommand(const CodaCallback &callBack,
+ const QByteArray &id,
+ const QVariant &cookie)
+{
+ sendRunControlResumeCommand(callBack, id, RM_RESUME, 1, 0, 0, cookie);
+}
+
+void CodaDevice::sendBreakpointsAddCommand(const CodaCallback &callBack,
+ const Breakpoint &bp,
+ const QVariant &cookie)
+{
+ QByteArray data;
+ JsonInputStream str(data);
+ str << bp;
+ sendCodaMessage(MessageWithReply, BreakpointsService, "add", data, callBack, cookie);
+}
+
+void CodaDevice::sendBreakpointsRemoveCommand(const CodaCallback &callBack,
+ const QByteArray &id,
+ const QVariant &cookie)
+{
+ sendBreakpointsRemoveCommand(callBack, QVector<QByteArray>(1, id), cookie);
+}
+
+void CodaDevice::sendBreakpointsRemoveCommand(const CodaCallback &callBack,
+ const QVector<QByteArray> &ids,
+ const QVariant &cookie)
+{
+ QByteArray data;
+ JsonInputStream str(data);
+ str << ids;
+ sendCodaMessage(MessageWithReply, BreakpointsService, "remove", data, callBack, cookie);
+}
+
+void CodaDevice::sendBreakpointsEnableCommand(const CodaCallback &callBack,
+ const QByteArray &id,
+ bool enable,
+ const QVariant &cookie)
+{
+ sendBreakpointsEnableCommand(callBack, QVector<QByteArray>(1, id), enable, cookie);
+}
+
+void CodaDevice::sendBreakpointsEnableCommand(const CodaCallback &callBack,
+ const QVector<QByteArray> &ids,
+ bool enable,
+ const QVariant &cookie)
+{
+ QByteArray data;
+ JsonInputStream str(data);
+ str << ids;
+ sendCodaMessage(MessageWithReply, BreakpointsService,
+ enable ? "enable" : "disable",
+ data, callBack, cookie);
+}
+
+void CodaDevice::sendMemorySetCommand(const CodaCallback &callBack,
+ const QByteArray &contextId,
+ quint64 start, const QByteArray& data,
+ const QVariant &cookie)
+{
+ QByteArray getData;
+ JsonInputStream str(getData);
+ // start/word size/mode. Mode should ideally be 1 (continue on error?)
+ str << contextId << '\0' << start << '\0' << 1 << '\0' << data.size() << '\0' << 1
+ << '\0' << data.toBase64();
+ sendCodaMessage(MessageWithReply, MemoryService, "set", getData, callBack, cookie);
+}
+
+void CodaDevice::sendMemoryGetCommand(const CodaCallback &callBack,
+ const QByteArray &contextId,
+ quint64 start, quint64 size,
+ const QVariant &cookie)
+{
+ QByteArray data;
+ JsonInputStream str(data);
+ // start/word size/mode. Mode should ideally be 1 (continue on error?)
+ str << contextId << '\0' << start << '\0' << 1 << '\0' << size << '\0' << 1;
+ sendCodaMessage(MessageWithReply, MemoryService, "get", data, callBack, cookie);
+}
+
+QByteArray CodaDevice::parseMemoryGet(const CodaCommandResult &r)
+{
+ if (r.type != CodaCommandResult::SuccessReply || r.values.size() < 1)
+ return QByteArray();
+ const JsonValue &memoryV = r.values.front();
+
+ if (memoryV.type() != JsonValue::String || memoryV.data().size() < 2
+ || !memoryV.data().endsWith('='))
+ return QByteArray();
+ // Catch errors reported as hash:
+ // R.4."TlVMTA==".{"Time":1276786871255,"Code":1,"AltCode":-38,"AltOrg":"POSIX","Format":"BadDescriptor"}
+ // Not sure what to make of it.
+ if (r.values.size() >= 2 && r.values.at(1).type() == JsonValue::Object)
+ qWarning("CodaDevice::parseMemoryGet(): Error retrieving memory: %s", r.values.at(1).toString(false).constData());
+ // decode
+ const QByteArray memory = QByteArray::fromBase64(memoryV.data());
+ if (memory.isEmpty())
+ qWarning("Base64 decoding of %s failed.", memoryV.data().constData());
+ if (debug)
+ qDebug("CodaDevice::parseMemoryGet: received %d bytes", memory.size());
+ return memory;
+}
+
+// Parse register children (array of names)
+QVector<QByteArray> CodaDevice::parseRegisterGetChildren(const CodaCommandResult &r)
+{
+ QVector<QByteArray> rc;
+ if (!r || r.values.size() < 1 || r.values.front().type() != JsonValue::Array)
+ return rc;
+ const JsonValue &front = r.values.front();
+ rc.reserve(front.childCount());
+ foreach(const JsonValue &v, front.children())
+ rc.push_back(v.data());
+ return rc;
+}
+
+CodaStatResponse CodaDevice::parseStat(const CodaCommandResult &r)
+{
+ CodaStatResponse rc;
+ if (!r || r.values.size() < 1 || r.values.front().type() != JsonValue::Object)
+ return rc;
+ foreach(const JsonValue &v, r.values.front().children()) {
+ if (v.name() == "Size") {
+ rc.size = v.data().toULongLong();
+ } else if (v.name() == "ATime") {
+ if (const quint64 atime = v.data().toULongLong())
+ rc.accessTime = CodaCommandResult::tcfTimeToQDateTime(atime);
+ } else if (v.name() == "MTime") {
+ if (const quint64 mtime = v.data().toULongLong())
+ rc.modTime = CodaCommandResult::tcfTimeToQDateTime(mtime);
+ }
+ }
+ return rc;
+}
+
+void CodaDevice::sendRegistersGetChildrenCommand(const CodaCallback &callBack,
+ const QByteArray &contextId,
+ const QVariant &cookie)
+{
+ QByteArray data;
+ JsonInputStream str(data);
+ str << contextId;
+ sendCodaMessage(MessageWithReply, RegistersService, "getChildren", data, callBack, cookie);
+}
+
+// Format id of register get request (needs contextId containing process and thread)
+static inline QByteArray registerId(const QByteArray &contextId, QByteArray id)
+{
+ QByteArray completeId = contextId;
+ if (!completeId.isEmpty())
+ completeId.append('.');
+ completeId.append(id);
+ return completeId;
+}
+
+// Format parameters of register get request
+static inline QByteArray registerGetData(const QByteArray &contextId, QByteArray id)
+{
+ QByteArray data;
+ JsonInputStream str(data);
+ str << registerId(contextId, id);
+ return data;
+}
+
+void CodaDevice::sendRegistersGetCommand(const CodaCallback &callBack,
+ const QByteArray &contextId,
+ QByteArray id,
+ const QVariant &cookie)
+{
+ sendCodaMessage(MessageWithReply, RegistersService, "get",
+ registerGetData(contextId, id), callBack, cookie);
+}
+
+void CodaDevice::sendRegistersGetMCommand(const CodaCallback &callBack,
+ const QByteArray &contextId,
+ const QVector<QByteArray> &ids,
+ const QVariant &cookie)
+{
+ // Format the register ids as a JSON list
+ QByteArray data;
+ JsonInputStream str(data);
+ str << '[';
+ const int count = ids.size();
+ for (int r = 0; r < count; r++) {
+ if (r)
+ str << ',';
+ // TODO: When 8-byte floating-point registers are supported, query for register length based on register id
+ str << '[' << registerId(contextId, ids.at(r)) << ',' << '0' << ',' << '4' << ']';
+ }
+ str << ']';
+ sendCodaMessage(MessageWithReply, RegistersService, "getm", data, callBack, cookie);
+}
+
+void CodaDevice::sendRegistersGetMRangeCommand(const CodaCallback &callBack,
+ const QByteArray &contextId,
+ unsigned start, unsigned count)
+{
+ const unsigned end = start + count;
+ if (end > (unsigned)d->m_registerNames.size()) {
+ qWarning("CodaDevice: No register name set for index %u (size: %d).", end, d->m_registerNames.size());
+ return;
+ }
+
+ QVector<QByteArray> ids;
+ ids.reserve(count);
+ for (unsigned i = start; i < end; ++i)
+ ids.push_back(d->m_registerNames.at(i));
+ sendRegistersGetMCommand(callBack, contextId, ids, QVariant(start));
+}
+
+// Set register
+void CodaDevice::sendRegistersSetCommand(const CodaCallback &callBack,
+ const QByteArray &contextId,
+ QByteArray id,
+ const QByteArray &value,
+ const QVariant &cookie)
+{
+ QByteArray data;
+ JsonInputStream str(data);
+ if (!contextId.isEmpty()) {
+ id.prepend('.');
+ id.prepend(contextId);
+ }
+ str << id << '\0' << value.toBase64();
+ sendCodaMessage(MessageWithReply, RegistersService, "set", data, callBack, cookie);
+}
+
+// Set register
+void CodaDevice::sendRegistersSetCommand(const CodaCallback &callBack,
+ const QByteArray &contextId,
+ unsigned registerNumber,
+ const QByteArray &value,
+ const QVariant &cookie)
+{
+ if (registerNumber >= (unsigned)d->m_registerNames.size()) {
+ qWarning("CodaDevice: No register name set for index %u (size: %d).", registerNumber, d->m_registerNames.size());
+ return;
+ }
+ sendRegistersSetCommand(callBack, contextId,
+ d->m_registerNames[registerNumber],
+ value, cookie);
+}
+
+static const char outputListenerIDC[] = "ProgramOutputConsoleLogger";
+
+void CodaDevice::sendLoggingAddListenerCommand(const CodaCallback &callBack,
+ const QVariant &cookie)
+{
+ QByteArray data;
+ JsonInputStream str(data);
+ str << outputListenerIDC;
+ sendCodaMessage(MessageWithReply, LoggingService, "addListener", data, callBack, cookie);
+}
+
+void CodaDevice::sendSymbianUninstallCommand(const Coda::CodaCallback &callBack,
+ const quint32 package,
+ const QVariant &cookie)
+{
+ QByteArray data;
+ JsonInputStream str(data);
+ QString string = QString::number(package, 16);
+ str << string;
+ sendCodaMessage(MessageWithReply, SymbianInstallService, "uninstall", data, callBack, cookie);
+}
+
+void CodaDevice::sendSymbianOsDataGetThreadsCommand(const CodaCallback &callBack,
+ const QVariant &cookie)
+{
+ QByteArray data;
+ sendCodaMessage(MessageWithReply, SymbianOSData, "getThreads", data, callBack, cookie);
+}
+
+void CodaDevice::sendSymbianOsDataFindProcessesCommand(const CodaCallback &callBack,
+ const QByteArray &processName,
+ const QByteArray &uid,
+ const QVariant &cookie)
+{
+ QByteArray data;
+ JsonInputStream str(data);
+ str << processName << '\0' << uid;
+ sendCodaMessage(MessageWithReply, SymbianOSData, "findRunningProcesses", data, callBack, cookie);
+}
+
+void CodaDevice::sendSymbianOsDataGetQtVersionCommand(const CodaCallback &callBack,
+ const QVariant &cookie)
+{
+ sendCodaMessage(MessageWithReply, SymbianOSData, "getQtVersion", QByteArray(), callBack, cookie);
+}
+
+void CodaDevice::sendSymbianOsDataGetRomInfoCommand(const CodaCallback &callBack,
+ const QVariant &cookie)
+{
+ sendCodaMessage(MessageWithReply, SymbianOSData, "getRomInfo", QByteArray(), callBack, cookie);
+}
+
+void CodaDevice::sendSymbianOsDataGetHalInfoCommand(const CodaCallback &callBack,
+ const QStringList &keys,
+ const QVariant &cookie)
+{
+ QByteArray data;
+ JsonInputStream str(data);
+ str << '[';
+ for (int i = 0; i < keys.count(); ++i) {
+ if (i)
+ str << ',';
+ str << keys[i];
+ }
+ str << ']';
+ sendCodaMessage(MessageWithReply, SymbianOSData, "getHalInfo", data, callBack, cookie);
+}
+
+void Coda::CodaDevice::sendFileSystemOpenCommand(const Coda::CodaCallback &callBack,
+ const QByteArray &name,
+ unsigned flags,
+ const QVariant &cookie)
+{
+ QByteArray data;
+ JsonInputStream str(data);
+ str << name << '\0' << flags << '\0' << '{' << '}';
+ sendCodaMessage(MessageWithReply, FileSystemService, "open", data, callBack, cookie);
+}
+
+void Coda::CodaDevice::sendFileSystemFstatCommand(const CodaCallback &callBack,
+ const QByteArray &handle,
+ const QVariant &cookie)
+{
+ QByteArray data;
+ JsonInputStream str(data);
+ str << handle;
+ sendCodaMessage(MessageWithReply, FileSystemService, "fstat", data, callBack, cookie);
+}
+
+void Coda::CodaDevice::sendFileSystemReadCommand(const Coda::CodaCallback &callBack,
+ const QByteArray &handle,
+ unsigned int offset,
+ unsigned int size,
+ const QVariant &cookie)
+{
+ QByteArray data;
+ JsonInputStream str(data);
+ str << handle << '\0' << offset << '\0' << size;
+ sendCodaMessage(MessageWithReply, FileSystemService, "read", data, callBack, cookie);
+}
+
+void Coda::CodaDevice::sendFileSystemWriteCommand(const Coda::CodaCallback &callBack,
+ const QByteArray &handle,
+ const QByteArray &dataIn,
+ unsigned offset,
+ const QVariant &cookie)
+{
+ QByteArray data;
+ JsonInputStream str(data);
+ str << handle << '\0' << offset << '\0' << dataIn.toBase64();
+ sendCodaMessage(MessageWithReply, FileSystemService, "write", data, callBack, cookie);
+}
+
+void Coda::CodaDevice::sendFileSystemCloseCommand(const Coda::CodaCallback &callBack,
+ const QByteArray &handle,
+ const QVariant &cookie)
+{
+ QByteArray data;
+ JsonInputStream str(data);
+ str << handle;
+ sendCodaMessage(MessageWithReply, FileSystemService, "close", data, callBack, cookie);
+}
+
+void Coda::CodaDevice::sendSymbianInstallSilentInstallCommand(const Coda::CodaCallback &callBack,
+ const QByteArray &file,
+ const QByteArray &targetDrive,
+ const QVariant &cookie)
+{
+ QByteArray data;
+ JsonInputStream str(data);
+ str << file << '\0' << targetDrive;
+ sendCodaMessage(MessageWithReply, SymbianInstallService, "install", data, callBack, cookie);
+}
+
+void Coda::CodaDevice::sendSymbianInstallUIInstallCommand(const Coda::CodaCallback &callBack,
+ const QByteArray &file,
+ const QVariant &cookie)
+{
+ QByteArray data;
+ JsonInputStream str(data);
+ str << file;
+ sendCodaMessage(MessageWithReply, SymbianInstallService, "installWithUI", data, callBack, cookie);
+}
+
+void Coda::CodaDevice::sendSymbianInstallGetPackageInfoCommand(const Coda::CodaCallback &callBack,
+ const QList<quint32> &packages,
+ const QVariant &cookie)
+{
+ QByteArray data;
+ JsonInputStream str(data);
+ str << '[';
+ for (int i = 0; i < packages.count(); ++i) {
+ if (i)
+ str << ',';
+ QString pkgString;
+ pkgString.setNum(packages[i], 16);
+ str << pkgString;
+ }
+ str << ']';
+ sendCodaMessage(MessageWithReply, SymbianInstallService, "getPackageInfo", data, callBack, cookie);
+}
+
+void Coda::CodaDevice::sendDebugSessionControlSessionStartCommand(const Coda::CodaCallback &callBack,
+ const QVariant &cookie)
+{
+ sendCodaMessage(MessageWithReply, DebugSessionControl, "sessionStart", QByteArray(), callBack, cookie);
+}
+
+void Coda::CodaDevice::sendDebugSessionControlSessionEndCommand(const Coda::CodaCallback &callBack,
+ const QVariant &cookie)
+{
+ sendCodaMessage(MessageWithReply, DebugSessionControl, "sessionEnd ", QByteArray(), callBack, cookie);
+}
+
+} // namespace Coda
diff --git a/tools/runonphone/symbianutils/codadevice.h b/tools/runonphone/symbianutils/codadevice.h
new file mode 100644
index 0000000..9b414ec
--- /dev/null
+++ b/tools/runonphone/symbianutils/codadevice.h
@@ -0,0 +1,447 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the tools applications of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, 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.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef CODAENGINE_H
+#define CODAENGINE_H
+
+#include "symbianutils_global.h"
+#include "codamessage.h"
+#include "callback.h"
+#include "json.h"
+
+#include <QtCore/QObject>
+#include <QtCore/QSharedPointer>
+#include <QtCore/QVector>
+#include <QtCore/QVariant>
+#include <QtCore/QStringList>
+#include <QtCore/QDateTime>
+
+QT_BEGIN_NAMESPACE
+class QIODevice;
+class QTextStream;
+QT_END_NAMESPACE
+
+namespace Coda {
+
+struct CodaDevicePrivate;
+struct Breakpoint;
+
+/* Command error handling in TCF:
+ * 1) 'Severe' errors (JSON format, parameter format): Trk emits a
+ * nonstandard message (\3\2 error parameters) and closes the connection.
+ * 2) Protocol errors: 'N' without error message is returned.
+ * 3) Errors in command execution: 'R' with a TCF error hash is returned
+ * (see CodaCommandError). */
+
+/* Error code return in 'R' reply to command
+ * (see top of 'Services' documentation). */
+struct SYMBIANUTILS_EXPORT CodaCommandError {
+ CodaCommandError();
+ void clear();
+ bool isError() const;
+ operator bool() const { return isError(); }
+ QString toString() const;
+ void write(QTextStream &str) const;
+ bool parse(const QVector<JsonValue> &values);
+
+ quint64 timeMS; // Since 1.1.1970
+ qint64 code;
+ QByteArray format; // message
+ // 'Alternative' meaning, like altOrg="POSIX"/altCode=<some errno>
+ QByteArray alternativeOrganization;
+ qint64 alternativeCode;
+};
+
+/* Answer to a Tcf command passed to the callback. */
+struct SYMBIANUTILS_EXPORT CodaCommandResult {
+ enum Type
+ {
+ SuccessReply, // 'R' and no error -> all happy.
+ CommandErrorReply, // 'R' with CodaCommandError received
+ ProgressReply, // 'P', progress indicator
+ FailReply // 'N' Protocol NAK, severe error
+ };
+
+ explicit CodaCommandResult(Type t = SuccessReply);
+ explicit CodaCommandResult(char typeChar, Services service,
+ const QByteArray &request,
+ const QVector<JsonValue> &values,
+ const QVariant &cookie);
+
+ QString toString() const;
+ QString errorString() const;
+ operator bool() const { return type == SuccessReply || type == ProgressReply; }
+
+ static QDateTime tcfTimeToQDateTime(quint64 tcfTimeMS);
+
+ Type type;
+ Services service;
+ QByteArray request;
+ CodaCommandError commandError;
+ QVector<JsonValue> values;
+ QVariant cookie;
+};
+
+// Response to stat/fstat
+struct SYMBIANUTILS_EXPORT CodaStatResponse
+{
+ CodaStatResponse();
+
+ quint64 size;
+ QDateTime modTime;
+ QDateTime accessTime;
+};
+
+typedef trk::Callback<const CodaCommandResult &> CodaCallback;
+
+/* CodaDevice: TCF communication helper using an asynchronous QIODevice
+ * implementing the TCF protocol according to:
+http://dev.eclipse.org/svnroot/dsdp/org.eclipse.tm.tcf/trunk/docs/TCF%20Specification.html
+http://dev.eclipse.org/svnroot/dsdp/org.eclipse.tm.tcf/trunk/docs/TCF%20Services.html
+ * Commands can be sent along with callbacks that are passed a
+ * CodaCommandResult and an opaque QVariant cookie. In addition, events are emitted.
+ *
+ * CODA notes:
+ * - Commands are accepted only after receiving the Locator Hello event
+ * - Serial communication initiation sequence:
+ * Send serial ping from host sendSerialPing() -> receive pong response with
+ * version information -> Send Locator Hello Event -> Receive Locator Hello Event
+ * -> Commands are accepted.
+ * - WLAN communication initiation sequence:
+ * Receive Locator Hello Event from CODA -> Commands are accepted.
+ */
+
+class SYMBIANUTILS_EXPORT CodaDevice : public QObject
+{
+ Q_PROPERTY(unsigned verbose READ verbose WRITE setVerbose)
+ Q_PROPERTY(bool serialFrame READ serialFrame WRITE setSerialFrame)
+ Q_OBJECT
+public:
+ // Flags for FileSystem:open
+ enum FileSystemOpenFlags
+ {
+ FileSystem_TCF_O_READ = 0x00000001,
+ FileSystem_TCF_O_WRITE = 0x00000002,
+ FileSystem_TCF_O_APPEND = 0x00000004,
+ FileSystem_TCF_O_CREAT = 0x00000008,
+ FileSystem_TCF_O_TRUNC = 0x00000010,
+ FileSystem_TCF_O_EXCL = 0x00000020
+ };
+
+ enum MessageType
+ {
+ MessageWithReply,
+ MessageWithoutReply, /* Non-standard: "Settings:set" command does not reply */
+ NoopMessage
+ };
+
+ typedef QSharedPointer<QIODevice> IODevicePtr;
+
+ explicit CodaDevice(QObject *parent = 0);
+ virtual ~CodaDevice();
+
+ unsigned verbose() const;
+ bool serialFrame() const;
+ void setSerialFrame(bool);
+
+ // Mapping of register names to indices for multi-requests.
+ // Register names can be retrieved via 'Registers:getChildren' (requires
+ // context id to be stripped).
+ QVector<QByteArray> registerNames() const;
+ void setRegisterNames(const QVector<QByteArray>& n);
+
+ IODevicePtr device() const;
+ IODevicePtr takeDevice();
+ void setDevice(const IODevicePtr &dp);
+
+ // Serial Only: Initiate communication. Will emit serialPong() signal with version.
+ void sendSerialPing(bool pingOnly = false);
+
+ // Send with parameters from string (which may contain '\0').
+ void sendCodaMessage(MessageType mt, Services service, const char *command,
+ const char *commandParameters, int commandParametersLength,
+ const CodaCallback &callBack = CodaCallback(),
+ const QVariant &cookie = QVariant());
+
+ void sendCodaMessage(MessageType mt, Services service, const char *command,
+ const QByteArray &commandParameters,
+ const CodaCallback &callBack = CodaCallback(),
+ const QVariant &cookie = QVariant());
+
+ // Convenience messages: Start a process
+ void sendProcessStartCommand(const CodaCallback &callBack,
+ const QString &binary,
+ unsigned uid,
+ QStringList arguments = QStringList(),
+ QString workingDirectory = QString(),
+ bool debugControl = true,
+ const QStringList &additionalLibraries = QStringList(),
+ const QVariant &cookie = QVariant());
+
+ // Just launch a process, don't attempt to attach the debugger to it
+ void sendRunProcessCommand(const CodaCallback &callBack,
+ const QString &processName,
+ QStringList arguments = QStringList(),
+ const QVariant &cookie = QVariant());
+
+ // Preferred over Processes:Terminate by TCF TRK.
+ void sendRunControlTerminateCommand(const CodaCallback &callBack,
+ const QByteArray &id,
+ const QVariant &cookie = QVariant());
+
+ void sendProcessTerminateCommand(const CodaCallback &callBack,
+ const QByteArray &id,
+ const QVariant &cookie = QVariant());
+
+ // Non-standard: Remove executable from settings.
+ // Probably needs to be called after stopping. This command has no response.
+ void sendSettingsRemoveExecutableCommand(const QString &binaryIn,
+ unsigned uid,
+ const QStringList &additionalLibraries = QStringList(),
+ const QVariant &cookie = QVariant());
+
+ void sendRunControlSuspendCommand(const CodaCallback &callBack,
+ const QByteArray &id,
+ const QVariant &cookie = QVariant());
+
+ // Resume / Step (see RunControlResumeMode).
+ void sendRunControlResumeCommand(const CodaCallback &callBack,
+ const QByteArray &id,
+ RunControlResumeMode mode,
+ unsigned count /* = 1, currently ignored. */,
+ quint64 rangeStart, quint64 rangeEnd,
+ const QVariant &cookie = QVariant());
+
+ // Convenience to resume a suspended process
+ void sendRunControlResumeCommand(const CodaCallback &callBack,
+ const QByteArray &id,
+ const QVariant &cookie = QVariant());
+
+ void sendBreakpointsAddCommand(const CodaCallback &callBack,
+ const Breakpoint &b,
+ const QVariant &cookie = QVariant());
+
+ void sendBreakpointsRemoveCommand(const CodaCallback &callBack,
+ const QByteArray &id,
+ const QVariant &cookie = QVariant());
+
+ void sendBreakpointsRemoveCommand(const CodaCallback &callBack,
+ const QVector<QByteArray> &id,
+ const QVariant &cookie = QVariant());
+
+ void sendBreakpointsEnableCommand(const CodaCallback &callBack,
+ const QByteArray &id,
+ bool enable,
+ const QVariant &cookie = QVariant());
+
+ void sendBreakpointsEnableCommand(const CodaCallback &callBack,
+ const QVector<QByteArray> &id,
+ bool enable,
+ const QVariant &cookie = QVariant());
+
+
+ void sendMemoryGetCommand(const CodaCallback &callBack,
+ const QByteArray &contextId,
+ quint64 start, quint64 size,
+ const QVariant &cookie = QVariant());
+
+ void sendMemorySetCommand(const CodaCallback &callBack,
+ const QByteArray &contextId,
+ quint64 start, const QByteArray& data,
+ const QVariant &cookie = QVariant());
+
+ // Get register names (children of context).
+ // It is possible to recurse from thread id down to single registers.
+ void sendRegistersGetChildrenCommand(const CodaCallback &callBack,
+ const QByteArray &contextId,
+ const QVariant &cookie = QVariant());
+
+ // Register get
+ void sendRegistersGetCommand(const CodaCallback &callBack,
+ const QByteArray &contextId,
+ QByteArray id,
+ const QVariant &cookie);
+
+ void sendRegistersGetMCommand(const CodaCallback &callBack,
+ const QByteArray &contextId,
+ const QVector<QByteArray> &ids,
+ const QVariant &cookie = QVariant());
+
+ // Convenience to get a range of register "R0" .. "R<n>".
+ // Cookie will be an int containing "start".
+ void sendRegistersGetMRangeCommand(const CodaCallback &callBack,
+ const QByteArray &contextId,
+ unsigned start, unsigned count);
+
+ // Set register
+ void sendRegistersSetCommand(const CodaCallback &callBack,
+ const QByteArray &contextId,
+ QByteArray ids,
+ const QByteArray &value, // binary value
+ const QVariant &cookie = QVariant());
+ // Set register
+ void sendRegistersSetCommand(const CodaCallback &callBack,
+ const QByteArray &contextId,
+ unsigned registerNumber,
+ const QByteArray &value, // binary value
+ const QVariant &cookie = QVariant());
+
+ // File System
+ void sendFileSystemOpenCommand(const CodaCallback &callBack,
+ const QByteArray &name,
+ unsigned flags = FileSystem_TCF_O_READ,
+ const QVariant &cookie = QVariant());
+
+ void sendFileSystemFstatCommand(const CodaCallback &callBack,
+ const QByteArray &handle,
+ const QVariant &cookie = QVariant());
+
+ void sendFileSystemReadCommand(const Coda::CodaCallback &callBack,
+ const QByteArray &handle,
+ unsigned int offset,
+ unsigned int size,
+ const QVariant &cookie = QVariant());
+
+ void sendFileSystemWriteCommand(const CodaCallback &callBack,
+ const QByteArray &handle,
+ const QByteArray &data,
+ unsigned offset = 0,
+ const QVariant &cookie = QVariant());
+
+ void sendFileSystemCloseCommand(const CodaCallback &callBack,
+ const QByteArray &handle,
+ const QVariant &cookie = QVariant());
+
+ // Symbian Install
+ void sendSymbianInstallSilentInstallCommand(const CodaCallback &callBack,
+ const QByteArray &file,
+ const QByteArray &targetDrive,
+ const QVariant &cookie = QVariant());
+
+ void sendSymbianInstallUIInstallCommand(const CodaCallback &callBack,
+ const QByteArray &file,
+ const QVariant &cookie = QVariant());
+
+ void sendSymbianInstallGetPackageInfoCommand(const Coda::CodaCallback &callBack,
+ const QList<quint32> &packages,
+ const QVariant &cookie = QVariant());
+
+ void sendLoggingAddListenerCommand(const CodaCallback &callBack,
+ const QVariant &cookie = QVariant());
+
+ void sendSymbianUninstallCommand(const Coda::CodaCallback &callBack,
+ const quint32 package,
+ const QVariant &cookie = QVariant());
+
+ // SymbianOs Data
+ void sendSymbianOsDataGetThreadsCommand(const CodaCallback &callBack,
+ const QVariant &cookie = QVariant());
+
+ void sendSymbianOsDataFindProcessesCommand(const CodaCallback &callBack,
+ const QByteArray &processName,
+ const QByteArray &uid,
+ const QVariant &cookie = QVariant());
+
+ void sendSymbianOsDataGetQtVersionCommand(const CodaCallback &callBack,
+ const QVariant &cookie = QVariant());
+
+ void sendSymbianOsDataGetRomInfoCommand(const CodaCallback &callBack,
+ const QVariant &cookie = QVariant());
+
+ void sendSymbianOsDataGetHalInfoCommand(const CodaCallback &callBack,
+ const QStringList &keys = QStringList(),
+ const QVariant &cookie = QVariant());
+
+ // DebugSessionControl
+ void sendDebugSessionControlSessionStartCommand(const CodaCallback &callBack,
+ const QVariant &cookie = QVariant());
+
+ void sendDebugSessionControlSessionEndCommand(const CodaCallback &callBack,
+ const QVariant &cookie = QVariant());
+
+ // Settings
+ void sendSettingsEnableLogCommand();
+
+ void writeCustomData(char protocolId, const QByteArray &aData);
+
+ static QByteArray parseMemoryGet(const CodaCommandResult &r);
+ static QVector<QByteArray> parseRegisterGetChildren(const CodaCommandResult &r);
+ static CodaStatResponse parseStat(const CodaCommandResult &r);
+
+signals:
+ void genericTcfEvent(int service, const QByteArray &name, const QVector<JsonValue> &value);
+ void tcfEvent(const Coda::CodaEvent &knownEvent);
+ void unknownEvent(uchar protocolId, const QByteArray& data);
+ void serialPong(const QString &codaVersion);
+
+ void logMessage(const QString &);
+ void error(const QString &);
+
+public slots:
+ void setVerbose(unsigned v);
+
+private slots:
+ void slotDeviceError();
+ void slotDeviceSocketStateChanged();
+ void slotDeviceReadyRead();
+
+private:
+ void deviceReadyReadSerial();
+ void deviceReadyReadTcp();
+
+ bool checkOpen();
+ void checkSendQueue();
+ void writeMessage(QByteArray data, bool ensureTerminating0 = true);
+ void emitLogMessage(const QString &);
+ inline int parseMessage(const QByteArray &);
+ void processMessage(const QByteArray &message);
+ inline void processSerialMessage(const QByteArray &message);
+ int parseTcfCommandReply(char type, const QVector<QByteArray> &tokens);
+ int parseTcfEvent(const QVector<QByteArray> &tokens);
+
+private:
+ QPair<int, int> findSerialHeader(QByteArray &in);
+ CodaDevicePrivate *d;
+};
+
+} // namespace Coda
+
+#endif // CODAENGINE_H
diff --git a/tools/runonphone/symbianutils/tcftrkmessage.cpp b/tools/runonphone/symbianutils/codamessage.cpp
index 9e9c16c..02dcd8b 100644
--- a/tools/runonphone/symbianutils/tcftrkmessage.cpp
+++ b/tools/runonphone/symbianutils/codamessage.cpp
@@ -7,29 +7,29 @@
** This file is part of the tools applications of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
** GNU Lesser General Public License Usage
-** 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.
+** Alternatively, 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.
**
** In addition, as a special exception, Nokia gives you certain additional
-** rights. These rights are described in the Nokia Qt LGPL Exception
+** rights. These rights are described in the Nokia Qt LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
-** GNU General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU General
-** Public License version 3.0 as published by the Free Software Foundation
-** and appearing in the file LICENSE.GPL included in the packaging of this
-** file. Please review the following information to ensure the GNU General
-** Public License version 3.0 requirements will be met:
-** http://www.gnu.org/copyleft/gpl.html.
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
**
-** 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.
**
**
**
@@ -39,7 +39,7 @@
**
****************************************************************************/
-#include "tcftrkmessage.h"
+#include "codamessage.h"
#include "json.h"
#include <QtCore/QString>
@@ -48,10 +48,11 @@
// Names matching the enum
static const char *serviceNamesC[] =
{ "Locator", "RunControl", "Processes", "Memory", "Settings", "Breakpoints",
- "Registers", "SimpleRegisters",
+ "Registers", "Logging", "FileSystem", "SymbianInstall", "SymbianOSData",
+ "DebugSessionControl",
"UnknownService"};
-namespace tcftrk {
+namespace Coda {
SYMBIANUTILS_EXPORT QString joinByteArrays(const QVector<QByteArray> &a, char sep)
{
@@ -341,7 +342,7 @@ QString Breakpoint::toString() const
JsonInputStream &operator<<(JsonInputStream &str, const Breakpoint &b)
{
if (b.contextIds.isEmpty())
- qWarning("tcftrk::Breakpoint: No context ids specified");
+ qWarning("Coda::Breakpoint: No context ids specified");
str << '{' << "ID" << ':' << QString::fromUtf8(b.id) << ','
<< "BreakpointType" << ':' << breakPointTypesC[b.type] << ','
@@ -356,27 +357,27 @@ JsonInputStream &operator<<(JsonInputStream &str, const Breakpoint &b)
}
// --- Events
-TcfTrkEvent::TcfTrkEvent(Type type) : m_type(type)
+CodaEvent::CodaEvent(Type type) : m_type(type)
{
}
-TcfTrkEvent::~TcfTrkEvent()
+CodaEvent::~CodaEvent()
{
}
-TcfTrkEvent::Type TcfTrkEvent::type() const
+CodaEvent::Type CodaEvent::type() const
{
return m_type;
}
-QString TcfTrkEvent::toString() const
+QString CodaEvent::toString() const
{
return QString();
}
static const char sharedLibrarySuspendReasonC[] = "Shared Library";
-TcfTrkEvent *TcfTrkEvent::parseEvent(Services s, const QByteArray &nameBA, const QVector<JsonValue> &values)
+CodaEvent *CodaEvent::parseEvent(Services s, const QByteArray &nameBA, const QVector<JsonValue> &values)
{
switch (s) {
case LocatorService:
@@ -384,7 +385,7 @@ TcfTrkEvent *TcfTrkEvent::parseEvent(Services s, const QByteArray &nameBA, const
QStringList services;
foreach (const JsonValue &jv, values.front().children())
services.push_back(QString::fromUtf8(jv.data()));
- return new TcfTrkLocatorHelloEvent(services);
+ return new CodaLocatorHelloEvent(services);
}
break;
case RunControlService:
@@ -395,67 +396,96 @@ TcfTrkEvent *TcfTrkEvent::parseEvent(Services s, const QByteArray &nameBA, const
const QByteArray idBA = values.at(0).data();
const quint64 pc = values.at(1).data().toULongLong();
const QByteArray reasonBA = values.at(2).data();
+ QByteArray messageBA;
// Module load: Special
if (reasonBA == sharedLibrarySuspendReasonC) {
ModuleLoadEventInfo info;
if (!info.parse(values.at(3)))
return 0;
- return new TcfTrkRunControlModuleLoadContextSuspendedEvent(idBA, reasonBA, pc, info);
+ return new CodaRunControlModuleLoadContextSuspendedEvent(idBA, reasonBA, pc, info);
+ } else {
+ // hash containing a 'message'-key with a verbose crash message.
+ if (values.at(3).type() == JsonValue::Object && values.at(3).childCount()
+ && values.at(3).children().at(0).type() == JsonValue::String)
+ messageBA = values.at(3).children().at(0).data();
}
- return new TcfTrkRunControlContextSuspendedEvent(idBA, reasonBA, pc);
+ return new CodaRunControlContextSuspendedEvent(idBA, reasonBA, messageBA, pc);
} // "contextSuspended"
if (nameBA == "contextAdded")
- return TcfTrkRunControlContextAddedEvent::parseEvent(values);
+ return CodaRunControlContextAddedEvent::parseEvent(values);
if (nameBA == "contextRemoved" && values.front().type() == JsonValue::Array) {
QVector<QByteArray> ids;
foreach(const JsonValue &c, values.front().children())
ids.push_back(c.data());
- return new TcfTrkRunControlContextRemovedEvent(ids);
+ return new CodaRunControlContextRemovedEvent(ids);
}
break;
+ case LoggingService:
+ if ((nameBA == "writeln" || nameBA == "write" /*not yet used*/) && values.size() >= 2)
+ return new CodaLoggingWriteEvent(values.at(0).data(), values.at(1).data());
+ break;
+ case ProcessesService:
+ if (nameBA == "exited" && values.size() >= 2)
+ return new CodaProcessExitedEvent(values.at(0).data());
+ break;
default:
break;
}
return 0;
}
-// -------------- TcfTrkServiceHelloEvent
-TcfTrkLocatorHelloEvent::TcfTrkLocatorHelloEvent(const QStringList &s) :
- TcfTrkEvent(LocatorHello),
+// -------------- CodaServiceHelloEvent
+CodaLocatorHelloEvent::CodaLocatorHelloEvent(const QStringList &s) :
+ CodaEvent(LocatorHello),
m_services(s)
{
}
-QString TcfTrkLocatorHelloEvent::toString() const
+QString CodaLocatorHelloEvent::toString() const
{
return QLatin1String("ServiceHello: ") + m_services.join(QLatin1String(", "));
}
-// -------------- TcfTrkIdEvent
-TcfTrkIdEvent::TcfTrkIdEvent(Type t, const QByteArray &id) :
- TcfTrkEvent(t), m_id(id)
+// -------------- Logging event
+
+CodaLoggingWriteEvent::CodaLoggingWriteEvent(const QByteArray &console, const QByteArray &message) :
+ CodaEvent(LoggingWriteEvent), m_console(console), m_message(message)
+{
+}
+
+QString CodaLoggingWriteEvent::toString() const
{
+ QByteArray msgBA = m_console;
+ msgBA += ": ";
+ msgBA += m_message;
+ return QString::fromUtf8(msgBA);
}
-// ---------- TcfTrkIdsEvent
-TcfTrkIdsEvent::TcfTrkIdsEvent(Type t, const QVector<QByteArray> &ids) :
- TcfTrkEvent(t), m_ids(ids)
+// -------------- CodaIdEvent
+CodaIdEvent::CodaIdEvent(Type t, const QByteArray &id) :
+ CodaEvent(t), m_id(id)
{
}
-QString TcfTrkIdsEvent::joinedIdString(const char sep) const
+// ---------- CodaIdsEvent
+CodaIdsEvent::CodaIdsEvent(Type t, const QVector<QByteArray> &ids) :
+ CodaEvent(t), m_ids(ids)
+{
+}
+
+QString CodaIdsEvent::joinedIdString(const char sep) const
{
return joinByteArrays(m_ids, sep);
}
-// ---------------- TcfTrkRunControlContextAddedEvent
-TcfTrkRunControlContextAddedEvent::TcfTrkRunControlContextAddedEvent(const RunControlContexts &c) :
- TcfTrkEvent(RunControlContextAdded), m_contexts(c)
+// ---------------- CodaRunControlContextAddedEvent
+CodaRunControlContextAddedEvent::CodaRunControlContextAddedEvent(const RunControlContexts &c) :
+ CodaEvent(RunControlContextAdded), m_contexts(c)
{
}
-TcfTrkRunControlContextAddedEvent
- *TcfTrkRunControlContextAddedEvent::parseEvent(const QVector<JsonValue> &values)
+CodaRunControlContextAddedEvent
+ *CodaRunControlContextAddedEvent::parseEvent(const QVector<JsonValue> &values)
{
// Parse array of contexts
if (values.size() < 1 || values.front().type() != JsonValue::Array)
@@ -467,10 +497,10 @@ TcfTrkRunControlContextAddedEvent
if (context.parse(v))
contexts.push_back(context);
}
- return new TcfTrkRunControlContextAddedEvent(contexts);
+ return new CodaRunControlContextAddedEvent(contexts);
}
-QString TcfTrkRunControlContextAddedEvent::toString() const
+QString CodaRunControlContextAddedEvent::toString() const
{
QString rc;
QTextStream str(&rc);
@@ -484,42 +514,45 @@ QString TcfTrkRunControlContextAddedEvent::toString() const
return rc;
}
-// --------------- TcfTrkRunControlContextRemovedEvent
-TcfTrkRunControlContextRemovedEvent::TcfTrkRunControlContextRemovedEvent(const QVector<QByteArray> &ids) :
- TcfTrkIdsEvent(RunControlContextRemoved, ids)
+// --------------- CodaRunControlContextRemovedEvent
+CodaRunControlContextRemovedEvent::CodaRunControlContextRemovedEvent(const QVector<QByteArray> &ids) :
+ CodaIdsEvent(RunControlContextRemoved, ids)
{
}
-QString TcfTrkRunControlContextRemovedEvent::toString() const
+QString CodaRunControlContextRemovedEvent::toString() const
{
return QLatin1String("RunControl: Removed contexts '") + joinedIdString() + ("'.");
}
-// --------------- TcfTrkRunControlContextSuspendedEvent
-TcfTrkRunControlContextSuspendedEvent::TcfTrkRunControlContextSuspendedEvent(const QByteArray &id,
+// --------------- CodaRunControlContextSuspendedEvent
+CodaRunControlContextSuspendedEvent::CodaRunControlContextSuspendedEvent(const QByteArray &id,
const QByteArray &reason,
+ const QByteArray &message,
quint64 pc) :
- TcfTrkIdEvent(RunControlSuspended, id), m_pc(pc), m_reason(reason)
+ CodaIdEvent(RunControlSuspended, id), m_pc(pc), m_reason(reason), m_message(message)
{
}
-TcfTrkRunControlContextSuspendedEvent::TcfTrkRunControlContextSuspendedEvent(Type t,
+CodaRunControlContextSuspendedEvent::CodaRunControlContextSuspendedEvent(Type t,
const QByteArray &id,
const QByteArray &reason,
quint64 pc) :
- TcfTrkIdEvent(t, id), m_pc(pc), m_reason(reason)
+ CodaIdEvent(t, id), m_pc(pc), m_reason(reason)
{
}
-void TcfTrkRunControlContextSuspendedEvent::format(QTextStream &str) const
+void CodaRunControlContextSuspendedEvent::format(QTextStream &str) const
{
str.setIntegerBase(16);
str << "RunControl: '" << idString() << "' suspended at 0x"
<< m_pc << ": '" << m_reason << "'.";
str.setIntegerBase(10);
+ if (!m_message.isEmpty())
+ str << " (" <<m_message << ')';
}
-QString TcfTrkRunControlContextSuspendedEvent::toString() const
+QString CodaRunControlContextSuspendedEvent::toString() const
{
QString rc;
QTextStream str(&rc);
@@ -527,36 +560,46 @@ QString TcfTrkRunControlContextSuspendedEvent::toString() const
return rc;
}
-TcfTrkRunControlContextSuspendedEvent::Reason TcfTrkRunControlContextSuspendedEvent::reason() const
+CodaRunControlContextSuspendedEvent::Reason CodaRunControlContextSuspendedEvent::reason() const
{
if (m_reason == sharedLibrarySuspendReasonC)
return ModuleLoad;
if (m_reason == "Breakpoint")
return BreakPoint;
// 'Data abort exception'/'Thread has panicked' ... unfortunately somewhat unspecific.
- if (m_reason.contains("exception") || m_reason.contains("panick"))
+ if (m_reason.contains("Exception") || m_reason.contains("panick"))
return Crash;
return Other;
}
-TcfTrkRunControlModuleLoadContextSuspendedEvent::TcfTrkRunControlModuleLoadContextSuspendedEvent(const QByteArray &id,
+CodaRunControlModuleLoadContextSuspendedEvent::CodaRunControlModuleLoadContextSuspendedEvent(const QByteArray &id,
const QByteArray &reason,
quint64 pc,
const ModuleLoadEventInfo &mi) :
- TcfTrkRunControlContextSuspendedEvent(RunControlModuleLoadSuspended, id, reason, pc),
+ CodaRunControlContextSuspendedEvent(RunControlModuleLoadSuspended, id, reason, pc),
m_mi(mi)
{
}
-QString TcfTrkRunControlModuleLoadContextSuspendedEvent::toString() const
+QString CodaRunControlModuleLoadContextSuspendedEvent::toString() const
{
QString rc;
QTextStream str(&rc);
- TcfTrkRunControlContextSuspendedEvent::format(str);
+ CodaRunControlContextSuspendedEvent::format(str);
str << ' ';
m_mi.format(str);
return rc;
}
+// -------------- CodaIdEvent
+CodaProcessExitedEvent::CodaProcessExitedEvent(const QByteArray &id) :
+ CodaEvent(ProcessExitedEvent), m_id(id)
+{
+}
+
+QString CodaProcessExitedEvent::toString() const
+{
+ return QString("Process \"%1\" exited").arg(idString());
+}
-} // namespace tcftrk
+} // namespace Coda
diff --git a/tools/runonphone/symbianutils/tcftrkmessage.h b/tools/runonphone/symbianutils/codamessage.h
index cee8584..fe72a1e 100644
--- a/tools/runonphone/symbianutils/tcftrkmessage.h
+++ b/tools/runonphone/symbianutils/codamessage.h
@@ -7,29 +7,29 @@
** This file is part of the tools applications of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
** GNU Lesser General Public License Usage
-** 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.
+** Alternatively, 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.
**
** In addition, as a special exception, Nokia gives you certain additional
-** rights. These rights are described in the Nokia Qt LGPL Exception
+** rights. These rights are described in the Nokia Qt LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
-** GNU General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU General
-** Public License version 3.0 as published by the Free Software Foundation
-** and appearing in the file LICENSE.GPL included in the packaging of this
-** file. Please review the following information to ensure the GNU General
-** Public License version 3.0 requirements will be met:
-** http://www.gnu.org/copyleft/gpl.html.
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
**
-** 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.
**
**
**
@@ -39,8 +39,8 @@
**
****************************************************************************/
-#ifndef TRCFTRKMESSAGE_H
-#define TRCFTRKMESSAGE_H
+#ifndef CODAMESSAGE_H
+#define CODAMESSAGE_H
#include "symbianutils_global.h"
@@ -51,7 +51,7 @@ QT_BEGIN_NAMESPACE
class QTextStream;
QT_END_NAMESPACE
-namespace tcftrk {
+namespace Coda {
class JsonValue;
class JsonInputStream;
@@ -61,10 +61,14 @@ enum Services {
RunControlService,
ProcessesService,
MemoryService,
- SettingsService, // non-standard, trk specific
+ SettingsService, // non-standard, CODA specific
BreakpointsService,
RegistersService,
- SimpleRegistersService, // non-standard, trk specific
+ LoggingService, // non-standard, CODA specific
+ FileSystemService,
+ SymbianInstallService, // non-standard, CODA specific
+ SymbianOSData, // non-standard, CODA specific
+ DebugSessionControl, // non-standard, CODA specific
UnknownService
}; // Note: Check string array 'serviceNamesC' of same size when modifying this.
@@ -138,7 +142,7 @@ struct SYMBIANUTILS_EXPORT ModuleLoadEventInfo {
bool requireResume;
};
-// Breakpoint as supported by TcfTrk source June 2010
+// Breakpoint as supported by Coda source June 2010
// TODO: Add watchpoints,etc once they are implemented
struct SYMBIANUTILS_EXPORT Breakpoint {
enum Type { Software, Hardware, Auto };
@@ -162,8 +166,8 @@ struct SYMBIANUTILS_EXPORT Breakpoint {
SYMBIANUTILS_EXPORT JsonInputStream &operator<<(JsonInputStream &str, const Breakpoint &b);
// Event hierarchy
-class SYMBIANUTILS_EXPORT TcfTrkEvent {
- Q_DISABLE_COPY(TcfTrkEvent)
+class SYMBIANUTILS_EXPORT CodaEvent {
+ Q_DISABLE_COPY(CodaEvent)
public:
enum Type { None,
LocatorHello,
@@ -172,40 +176,57 @@ public:
RunControlSuspended,
RunControlBreakpointSuspended,
RunControlModuleLoadSuspended,
- RunControlResumed
+ RunControlResumed,
+ LoggingWriteEvent, // Non-standard
+ ProcessExitedEvent // Non-standard
};
- virtual ~TcfTrkEvent();
+ virtual ~CodaEvent();
Type type() const;
virtual QString toString() const;
- static TcfTrkEvent *parseEvent(Services s, const QByteArray &name, const QVector<JsonValue> &val);
+ static CodaEvent *parseEvent(Services s, const QByteArray &name, const QVector<JsonValue> &val);
protected:
- explicit TcfTrkEvent(Type type = None);
+ explicit CodaEvent(Type type = None);
private:
const Type m_type;
};
// ServiceHello
-class SYMBIANUTILS_EXPORT TcfTrkLocatorHelloEvent : public TcfTrkEvent {
+class SYMBIANUTILS_EXPORT CodaLocatorHelloEvent : public CodaEvent {
public:
- explicit TcfTrkLocatorHelloEvent(const QStringList &);
+ explicit CodaLocatorHelloEvent(const QStringList &);
- const QStringList &services() { return m_services; }
+ const QStringList &services() const { return m_services; }
virtual QString toString() const;
private:
QStringList m_services;
};
+// Logging event (non-standard, CODA specific)
+class SYMBIANUTILS_EXPORT CodaLoggingWriteEvent : public CodaEvent {
+public:
+ explicit CodaLoggingWriteEvent(const QByteArray &console, const QByteArray &message);
+
+ QByteArray message() const { return m_message; }
+ QByteArray console() const { return m_console; }
+
+ virtual QString toString() const;
+
+private:
+ const QByteArray m_console;
+ const QByteArray m_message;
+};
+
// Base for events that just have one id as parameter
// (simple suspend)
-class SYMBIANUTILS_EXPORT TcfTrkIdEvent : public TcfTrkEvent {
+class SYMBIANUTILS_EXPORT CodaIdEvent : public CodaEvent {
protected:
- explicit TcfTrkIdEvent(Type t, const QByteArray &id);
+ explicit CodaIdEvent(Type t, const QByteArray &id);
public:
QByteArray id() const { return m_id; }
QString idString() const { return QString::fromUtf8(m_id); }
@@ -216,9 +237,9 @@ private:
// Base for events that just have some ids as parameter
// (context removed)
-class SYMBIANUTILS_EXPORT TcfTrkIdsEvent : public TcfTrkEvent {
+class SYMBIANUTILS_EXPORT CodaIdsEvent : public CodaEvent {
protected:
- explicit TcfTrkIdsEvent(Type t, const QVector<QByteArray> &ids);
+ explicit CodaIdsEvent(Type t, const QVector<QByteArray> &ids);
public:
QVector<QByteArray> ids() const { return m_ids; }
@@ -229,44 +250,46 @@ private:
};
// RunControlContextAdded
-class SYMBIANUTILS_EXPORT TcfTrkRunControlContextAddedEvent : public TcfTrkEvent {
+class SYMBIANUTILS_EXPORT CodaRunControlContextAddedEvent : public CodaEvent {
public:
typedef QVector<RunControlContext> RunControlContexts;
- explicit TcfTrkRunControlContextAddedEvent(const RunControlContexts &c);
+ explicit CodaRunControlContextAddedEvent(const RunControlContexts &c);
const RunControlContexts &contexts() const { return m_contexts; }
virtual QString toString() const;
- static TcfTrkRunControlContextAddedEvent *parseEvent(const QVector<JsonValue> &val);
+ static CodaRunControlContextAddedEvent *parseEvent(const QVector<JsonValue> &val);
private:
const RunControlContexts m_contexts;
};
// RunControlContextRemoved
-class SYMBIANUTILS_EXPORT TcfTrkRunControlContextRemovedEvent : public TcfTrkIdsEvent {
+class SYMBIANUTILS_EXPORT CodaRunControlContextRemovedEvent : public CodaIdsEvent {
public:
- explicit TcfTrkRunControlContextRemovedEvent(const QVector<QByteArray> &id);
+ explicit CodaRunControlContextRemovedEvent(const QVector<QByteArray> &id);
virtual QString toString() const;
};
// Simple RunControlContextSuspended (process/thread)
-class SYMBIANUTILS_EXPORT TcfTrkRunControlContextSuspendedEvent : public TcfTrkIdEvent {
+class SYMBIANUTILS_EXPORT CodaRunControlContextSuspendedEvent : public CodaIdEvent {
public:
enum Reason { BreakPoint, ModuleLoad, Crash, Other } ;
- explicit TcfTrkRunControlContextSuspendedEvent(const QByteArray &id,
+ explicit CodaRunControlContextSuspendedEvent(const QByteArray &id,
const QByteArray &reason,
+ const QByteArray &message,
quint64 pc = 0);
virtual QString toString() const;
quint64 pc() const { return m_pc; }
QByteArray reasonID() const { return m_reason; }
Reason reason() const;
+ QByteArray message() const { return m_message; }
protected:
- explicit TcfTrkRunControlContextSuspendedEvent(Type t,
+ explicit CodaRunControlContextSuspendedEvent(Type t,
const QByteArray &id,
const QByteArray &reason,
quint64 pc = 0);
@@ -275,12 +298,13 @@ protected:
private:
const quint64 m_pc;
const QByteArray m_reason;
+ const QByteArray m_message;
};
// RunControlContextSuspended due to module load
-class SYMBIANUTILS_EXPORT TcfTrkRunControlModuleLoadContextSuspendedEvent : public TcfTrkRunControlContextSuspendedEvent {
+class SYMBIANUTILS_EXPORT CodaRunControlModuleLoadContextSuspendedEvent : public CodaRunControlContextSuspendedEvent {
public:
- explicit TcfTrkRunControlModuleLoadContextSuspendedEvent(const QByteArray &id,
+ explicit CodaRunControlModuleLoadContextSuspendedEvent(const QByteArray &id,
const QByteArray &reason,
quint64 pc,
const ModuleLoadEventInfo &mi);
@@ -292,5 +316,18 @@ private:
const ModuleLoadEventInfo m_mi;
};
-} // namespace tcftrk
-#endif // TRCFTRKMESSAGE_H
+// Process exited event
+class SYMBIANUTILS_EXPORT CodaProcessExitedEvent : public CodaEvent {
+public:
+ explicit CodaProcessExitedEvent(const QByteArray &id);
+
+ QByteArray id() const { return m_id; }
+ QString idString() const { return QString::fromUtf8(m_id); }
+ virtual QString toString() const;
+
+private:
+ const QByteArray m_id;
+};
+
+} // namespace Coda
+#endif // CODAMESSAGE_H
diff --git a/tools/runonphone/symbianutils/json.cpp b/tools/runonphone/symbianutils/json.cpp
index a2d53ce..93f9395 100644
--- a/tools/runonphone/symbianutils/json.cpp
+++ b/tools/runonphone/symbianutils/json.cpp
@@ -49,6 +49,7 @@
#include <QtCore/QTextStream>
#include <QtCore/QDebug>
#include <QtCore/QStringList>
+#include <QtCore/QVariant>
#include <ctype.h>
@@ -59,7 +60,7 @@
#define JDEBUG(s)
#endif
-namespace tcftrk {
+namespace Coda {
static void skipSpaces(const char *&from, const char *to)
{
@@ -100,6 +101,7 @@ QByteArray JsonValue::parseNumber(const char *&from, const char *to)
QByteArray JsonValue::parseCString(const char *&from, const char *to)
{
QByteArray result;
+ const char * const fromSaved = from;
JDEBUG("parseCString: " << QByteArray(from, to - from));
if (*from != '"') {
qDebug() << "JSON Parse Error, double quote expected";
@@ -117,7 +119,8 @@ QByteArray JsonValue::parseCString(const char *&from, const char *to)
if (*ptr == '\\') {
++ptr;
if (ptr == to) {
- qDebug() << "JSON Parse Error, unterminated backslash escape";
+ qWarning("JSON Parse Error, unterminated backslash escape in '%s'",
+ QByteArray(fromSaved, to - fromSaved).constData());
from = ptr; // So we don't hang
return QByteArray();
}
@@ -142,8 +145,24 @@ QByteArray JsonValue::parseCString(const char *&from, const char *to)
case 'v': *dst++ = '\v'; break;
case '"': *dst++ = '"'; break;
case '\\': *dst++ = '\\'; break;
- default:
- {
+ case 'u': { // 4 digit hex escape as in '\u000a'
+ if (end - src < 4) {
+ qWarning("JSON Parse Error, too few hex digits in \\u-escape in '%s' obtained from '%s'",
+ result.constData(), QByteArray(fromSaved, to - fromSaved).constData());
+ return QByteArray();
+ }
+ bool ok;
+ const uchar prod = QByteArray(src, 4).toUInt(&ok, 16);
+ if (!ok) {
+ qWarning("JSON Parse Error, invalid hex digits in \\u-escape in '%s' obtained from '%s'",
+ result.constData(), QByteArray(fromSaved, to - fromSaved).constData());
+ return QByteArray();
+ }
+ *dst++ = prod;
+ src += 4;
+ }
+ break;
+ default: { // Up to 3 decimal digits: Not sure if this is supported in JSON?
int chars = 0;
uchar prod = 0;
forever {
@@ -157,7 +176,8 @@ QByteArray JsonValue::parseCString(const char *&from, const char *to)
c = *src++;
}
if (!chars) {
- qDebug() << "JSON Parse Error, unrecognized backslash escape";
+ qWarning("JSON Parse Error, unrecognized backslash escape in string '%s' obtained from '%s'",
+ result.constData(), QByteArray(fromSaved, to - fromSaved).constData());
return QByteArray();
}
*dst++ = prod;
@@ -360,7 +380,7 @@ QByteArray JsonValue::toString(bool multiline, int indent) const
break;
case String:
if (!m_name.isEmpty())
- result += m_name + "=";
+ result += m_name + '=';
result += '"' + escapeCString(m_data) + '"';
break;
case Number:
@@ -380,30 +400,69 @@ QByteArray JsonValue::toString(bool multiline, int indent) const
if (multiline) {
result += "{\n";
dumpChildren(&result, multiline, indent + 1);
- result += '\n' + ind(indent) + "}";
+ result += '\n' + ind(indent) + '}';
} else {
- result += "{";
+ result += '{';
dumpChildren(&result, multiline, indent + 1);
- result += "}";
+ result += '}';
}
break;
case Array:
if (!m_name.isEmpty())
- result += m_name + "=";
+ result += m_name + '=';
if (multiline) {
result += "[\n";
dumpChildren(&result, multiline, indent + 1);
- result += '\n' + ind(indent) + "]";
+ result += '\n' + ind(indent) + ']';
} else {
- result += "[";
+ result += '[';
dumpChildren(&result, multiline, indent + 1);
- result += "]";
+ result += ']';
}
break;
}
return result;
}
+
+QVariant JsonValue::toVariant() const
+{
+ switch (m_type) {
+ case String:
+ return QString(m_data);
+ case Number: {
+ bool ok;
+ qint64 val = QString(m_data).toLongLong(&ok);
+ if (ok)
+ return val;
+ return QVariant();
+ }
+ case Object: {
+ QHash<QString, QVariant> hash;
+ for (int i = 0; i < m_children.size(); ++i) {
+ QString name(m_children[i].name());
+ QVariant val = m_children[i].toVariant();
+ hash.insert(name, val);
+ }
+ return hash;
+ }
+ case Array: {
+ QList<QVariant> list;
+ for (int i = 0; i < m_children.size(); ++i) {
+ list.append(m_children[i].toVariant());
+ }
+ return list;
+ }
+ case Boolean:
+ return data() == QByteArray("true");
+ case Invalid:
+ case NullObject:
+ default:
+ return QVariant();
+ }
+}
+
+
void JsonValue::fromString(const QByteArray &ba)
{
const char *from = ba.constBegin();
@@ -486,5 +545,5 @@ JsonInputStream &JsonInputStream::operator<<(bool b)
return *this;
}
-} // namespace tcftrk
+} // namespace Coda
diff --git a/tools/runonphone/symbianutils/json.h b/tools/runonphone/symbianutils/json.h
index ca9aebf..11273e0 100644
--- a/tools/runonphone/symbianutils/json.h
+++ b/tools/runonphone/symbianutils/json.h
@@ -48,7 +48,7 @@
#include <QtCore/QStringList>
#include <QtCore/QVector>
-namespace tcftrk {
+namespace Coda {
class SYMBIANUTILS_EXPORT JsonValue
{
@@ -67,7 +67,7 @@ public:
Boolean,
Object,
NullObject,
- Array,
+ Array
};
Type m_type;
@@ -95,6 +95,8 @@ public:
void fromString(const QByteArray &str);
void setStreamOutput(const QByteArray &name, const QByteArray &content);
+ QVariant toVariant() const;
+
private:
static QByteArray parseCString(const char *&from, const char *to);
static QByteArray parseNumber(const char *&from, const char *to);
@@ -144,6 +146,6 @@ private:
QByteArray &m_target;
};
-} // namespace tcftrk
+} // namespace Coda
#endif // SYMBIANUTILS_JSON_H
diff --git a/tools/runonphone/symbianutils/launcher.cpp b/tools/runonphone/symbianutils/launcher.cpp
index e52cf28..ff67881 100644
--- a/tools/runonphone/symbianutils/launcher.cpp
+++ b/tools/runonphone/symbianutils/launcher.cpp
@@ -52,6 +52,7 @@
#include <QtCore/QDebug>
#include <QtCore/QQueue>
#include <QtCore/QFile>
+#include <QtCore/QFileInfo>
#include <QtCore/QScopedPointer>
#include <cstdio>
@@ -86,15 +87,24 @@ void CrashReportState::clear()
}
struct LauncherPrivate {
- struct CopyState {
- QString sourceFileName;
- QString destinationFileName;
+ struct TransferState {
+ int currentFileName;
uint copyFileHandle;
QScopedPointer<QByteArray> data;
qint64 position;
QScopedPointer<QFile> localFile;
};
+ struct CopyState : public TransferState {
+ QStringList sourceFileNames;
+ QStringList destinationFileNames;
+ };
+
+ struct DownloadState : public TransferState {
+ QString sourceFileName;
+ QString destinationFileName;
+ };
+
explicit LauncherPrivate(const TrkDevicePtr &d);
TrkDevicePtr m_device;
@@ -106,21 +116,28 @@ struct LauncherPrivate {
Session m_session; // global-ish data (process id, target information)
CopyState m_copyState;
- CopyState m_downloadState;
+ DownloadState m_downloadState;
QString m_fileName;
- QStringList m_commandLineArgs;
- QString m_installFileName;
+ QString m_commandLineArgs;
+ QStringList m_installFileNames;
+ int m_currentInstallFileName;
int m_verbose;
Launcher::Actions m_startupActions;
bool m_closeDevice;
CrashReportState m_crashReportState;
+ Launcher::InstallationMode m_installationMode;
+ Launcher::InstallationMode m_currentInstallationStep;
+ char m_installationDrive;
};
LauncherPrivate::LauncherPrivate(const TrkDevicePtr &d) :
m_device(d),
m_state(Launcher::Disconnected),
m_verbose(0),
- m_closeDevice(true)
+ m_closeDevice(true),
+ m_installationMode(Launcher::InstallationModeSilentAndUser),
+ m_currentInstallationStep(Launcher::InstallationModeSilent),
+ m_installationDrive('C')
{
if (m_device.isNull())
m_device = TrkDevicePtr(new TrkDevice);
@@ -159,6 +176,16 @@ void Launcher::setState(State s)
}
}
+void Launcher::setInstallationMode(InstallationMode installation)
+{
+ d->m_installationMode = installation;
+}
+
+void Launcher::setInstallationDrive(char drive)
+{
+ d->m_installationDrive = drive;
+}
+
void Launcher::addStartupActions(trk::Launcher::Actions startupActions)
{
d->m_startupActions = Actions(d->m_startupActions | startupActions);
@@ -184,10 +211,11 @@ void Launcher::setFileName(const QString &name)
d->m_fileName = name;
}
-void Launcher::setCopyFileName(const QString &srcName, const QString &dstName)
+void Launcher::setCopyFileNames(const QStringList &srcNames, const QStringList &dstNames)
{
- d->m_copyState.sourceFileName = srcName;
- d->m_copyState.destinationFileName = dstName;
+ d->m_copyState.sourceFileNames = srcNames;
+ d->m_copyState.destinationFileNames = dstNames;
+ d->m_copyState.currentFileName = 0;
}
void Launcher::setDownloadFileName(const QString &srcName, const QString &dstName)
@@ -196,12 +224,13 @@ void Launcher::setDownloadFileName(const QString &srcName, const QString &dstNam
d->m_downloadState.destinationFileName = dstName;
}
-void Launcher::setInstallFileName(const QString &name)
+void Launcher::setInstallFileNames(const QStringList &names)
{
- d->m_installFileName = name;
+ d->m_installFileNames = names;
+ d->m_currentInstallFileName = 0;
}
-void Launcher::setCommandLineArgs(const QStringList &args)
+void Launcher::setCommandLineArgs(const QString &args)
{
d->m_commandLineArgs = args;
}
@@ -227,6 +256,16 @@ void Launcher::setCloseDevice(bool c)
d->m_closeDevice = c;
}
+Launcher::InstallationMode Launcher::installationMode() const
+{
+ return d->m_installationMode;
+}
+
+char Launcher::installationDrive() const
+{
+ return d->m_installationDrive;
+}
+
bool Launcher::startServer(QString *errorMessage)
{
errorMessage->clear();
@@ -241,29 +280,30 @@ bool Launcher::startServer(QString *errorMessage)
if (!d->m_fileName.isEmpty())
str << " Executable=" << d->m_fileName;
if (!d->m_commandLineArgs.isEmpty())
- str << " Arguments= " << d->m_commandLineArgs.join(QString(QLatin1Char(' ')));
- if (!d->m_copyState.sourceFileName.isEmpty())
- str << " Package/Source=" << d->m_copyState.sourceFileName;
- if (!d->m_copyState.destinationFileName.isEmpty())
- str << " Remote Package/Destination=" << d->m_copyState.destinationFileName;
+ str << " Arguments= " << d->m_commandLineArgs;
+ for (int i = 0; i < d->m_copyState.sourceFileNames.size(); ++i) {
+ str << " Package/Source=" << d->m_copyState.sourceFileNames.at(i);
+ str << " Remote Package/Destination=" << d->m_copyState.destinationFileNames.at(i);
+ }
if (!d->m_downloadState.sourceFileName.isEmpty())
str << " Source=" << d->m_downloadState.sourceFileName;
if (!d->m_downloadState.destinationFileName.isEmpty())
str << " Destination=" << d->m_downloadState.destinationFileName;
- if (!d->m_installFileName.isEmpty())
- str << " Install file=" << d->m_installFileName;
+ if (!d->m_installFileNames.isEmpty())
+ foreach (const QString &installFileName, d->m_installFileNames)
+ str << " Install file=" << installFileName;
logMessage(msg);
}
if (d->m_startupActions & ActionCopy) {
- if (d->m_copyState.sourceFileName.isEmpty()) {
+ if (d->m_copyState.sourceFileNames.isEmpty()) {
qWarning("No local filename given for copying package.");
return false;
- } else if (d->m_copyState.destinationFileName.isEmpty()) {
+ } else if (d->m_copyState.destinationFileNames.isEmpty()) {
qWarning("No remote filename given for copying package.");
return false;
}
}
- if (d->m_startupActions & ActionInstall && d->m_installFileName.isEmpty()) {
+ if (d->m_startupActions & ActionInstall && d->m_installFileNames.isEmpty()) {
qWarning("No package name given for installing.");
return false;
}
@@ -303,7 +343,7 @@ void Launcher::handleConnect(const TrkResult &result)
if (d->m_startupActions & ActionCopy)
copyFileToRemote();
else if (d->m_startupActions & ActionInstall)
- installRemotePackageSilently();
+ installRemotePackage();
else if (d->m_startupActions & ActionRun)
startInferiorIfNeeded();
else if (d->m_startupActions & ActionDownload)
@@ -416,9 +456,9 @@ void Launcher::handleResult(const TrkResult &result)
case TrkNotifyAck:
break;
case TrkNotifyNak: { // NAK
- logMessage(prefix + "NAK: " + str);
+ logMessage(prefix + QLatin1String("NAK: ") + str);
//logMessage(prefix << "TOKEN: " << result.token);
- logMessage(prefix + "ERROR: " + errorMessage(result.data.at(0)));
+ logMessage(prefix + QLatin1String("ERROR: ") + errorMessage(result.data.at(0)));
break;
}
case TrkNotifyStopped: { // Notified Stopped
@@ -433,12 +473,12 @@ void Launcher::handleResult(const TrkResult &result)
break;
}
case TrkNotifyException: { // Notify Exception (obsolete)
- logMessage(prefix + "NOTE: EXCEPTION " + str);
+ logMessage(prefix + QLatin1String("NOTE: EXCEPTION ") + str);
d->m_device->sendTrkAck(result.token);
break;
}
case TrkNotifyInternalError: { //
- logMessage(prefix + "NOTE: INTERNAL ERROR: " + str);
+ logMessage(prefix + QLatin1String("NOTE: INTERNAL ERROR: ") + str);
d->m_device->sendTrkAck(result.token);
break;
}
@@ -497,22 +537,22 @@ void Launcher::handleResult(const TrkResult &result)
break;
}
case TrkNotifyProcessorStarted: { // NotifyProcessorStarted
- logMessage(prefix + "NOTE: PROCESSOR STARTED: " + str);
+ logMessage(prefix + QLatin1String("NOTE: PROCESSOR STARTED: ") + str);
d->m_device->sendTrkAck(result.token);
break;
}
case TrkNotifyProcessorStandBy: { // NotifyProcessorStandby
- logMessage(prefix + "NOTE: PROCESSOR STANDBY: " + str);
+ logMessage(prefix + QLatin1String("NOTE: PROCESSOR STANDBY: ") + str);
d->m_device->sendTrkAck(result.token);
break;
}
case TrkNotifyProcessorReset: { // NotifyProcessorReset
- logMessage(prefix + "NOTE: PROCESSOR RESET: " + str);
+ logMessage(prefix + QLatin1String("NOTE: PROCESSOR RESET: ") + str);
d->m_device->sendTrkAck(result.token);
break;
}
default: {
- logMessage(prefix + "INVALID: " + str);
+ logMessage(prefix + QLatin1String("INVALID: ") + str);
break;
}
}
@@ -560,15 +600,15 @@ static inline QString msgCannotOpenLocalFile(const QString &fileName, const QStr
void Launcher::handleFileCreation(const TrkResult &result)
{
if (result.errorCode() || result.data.size() < 6) {
- const QString msg = msgCannotOpenRemoteFile(d->m_copyState.destinationFileName, result.errorString());
+ const QString msg = msgCannotOpenRemoteFile(d->m_copyState.destinationFileNames.at(d->m_copyState.currentFileName), result.errorString());
logMessage(msg);
- emit canNotCreateFile(d->m_copyState.destinationFileName, msg);
+ emit canNotCreateFile(d->m_copyState.destinationFileNames.at(d->m_copyState.currentFileName), msg);
disconnectTrk();
return;
}
const char *data = result.data.data();
d->m_copyState.copyFileHandle = extractInt(data + 2);
- const QString localFileName = d->m_copyState.sourceFileName;
+ const QString localFileName = d->m_copyState.sourceFileNames.at(d->m_copyState.currentFileName);
QFile file(localFileName);
d->m_copyState.position = 0;
if (!file.open(QIODevice::ReadOnly)) {
@@ -644,7 +684,7 @@ void Launcher::handleCopy(const TrkResult &result)
{
if (result.errorCode() || result.data.size() < 4) {
closeRemoteFile(true);
- emit canNotWriteFile(d->m_copyState.destinationFileName, result.errorString());
+ emit canNotWriteFile(d->m_copyState.destinationFileNames.at(d->m_copyState.currentFileName), result.errorString());
disconnectTrk();
} else {
continueCopying(extractShort(result.data.data() + 2));
@@ -680,7 +720,7 @@ void Launcher::closeRemoteFile(bool failed)
d->m_device->sendTrkMessage(TrkCloseFile,
failed ? TrkCallback() : TrkCallback(this, &Launcher::handleFileCopied),
ba);
- d->m_copyState.data.reset();
+ d->m_copyState.data.reset(0);
d->m_copyState.copyFileHandle = 0;
d->m_copyState.position = 0;
}
@@ -688,15 +728,21 @@ void Launcher::closeRemoteFile(bool failed)
void Launcher::handleFileCopied(const TrkResult &result)
{
if (result.errorCode())
- emit canNotCloseFile(d->m_copyState.destinationFileName, result.errorString());
- if (d->m_startupActions & ActionInstall)
- installRemotePackageSilently();
- else if (d->m_startupActions & ActionRun)
+ emit canNotCloseFile(d->m_copyState.destinationFileNames.at(d->m_copyState.currentFileName), result.errorString());
+
+ ++d->m_copyState.currentFileName;
+
+ if (d->m_startupActions & ActionInstall && d->m_copyState.currentFileName < d->m_copyState.sourceFileNames.size()) {
+ copyFileToRemote();
+ } else if (d->m_startupActions & ActionInstall) {
+ installRemotePackage();
+ } else if (d->m_startupActions & ActionRun) {
startInferiorIfNeeded();
- else if (d->m_startupActions & ActionDownload)
+ } else if (d->m_startupActions & ActionDownload) {
copyFileFromRemote();
- else
+ } else {
disconnectTrk();
+ }
}
void Launcher::handleCpuType(const TrkResult &result)
@@ -840,16 +886,18 @@ void Launcher::disconnectTrk()
void Launcher::copyFileToRemote()
{
- emit copyingStarted();
+ QFileInfo fileInfo(d->m_copyState.destinationFileNames.at(d->m_copyState.currentFileName));
+ emit copyingStarted(fileInfo.fileName());
QByteArray ba;
ba.append(char(10)); //kDSFileOpenWrite | kDSFileOpenBinary
- appendString(&ba, d->m_copyState.destinationFileName.toLocal8Bit(), TargetByteOrder, false);
+ appendString(&ba, d->m_copyState.destinationFileNames.at(d->m_copyState.currentFileName).toLocal8Bit(), TargetByteOrder, false);
d->m_device->sendTrkMessage(TrkOpenFile, TrkCallback(this, &Launcher::handleFileCreation), ba);
}
void Launcher::copyFileFromRemote()
{
- emit copyingStarted();
+ QFileInfo fileInfo(d->m_downloadState.sourceFileName);
+ emit copyingStarted(fileInfo.fileName());
QByteArray ba;
ba.append(char(9)); //kDSFileOpenRead | kDSFileOpenBinary
appendString(&ba, d->m_downloadState.sourceFileName.toLocal8Bit(), TargetByteOrder, false);
@@ -858,23 +906,59 @@ void Launcher::copyFileFromRemote()
void Launcher::installRemotePackageSilently()
{
- emit installingStarted();
+ emit installingStarted(d->m_installFileNames.at(d->m_currentInstallFileName));
+ d->m_currentInstallationStep = InstallationModeSilent;
QByteArray ba;
- ba.append('C');
- appendString(&ba, d->m_installFileName.toLocal8Bit(), TargetByteOrder, false);
+ ba.append(static_cast<char>(QChar::toUpper((ushort)d->m_installationDrive)));
+ appendString(&ba, d->m_installFileNames.at(d->m_currentInstallFileName).toLocal8Bit(), TargetByteOrder, false);
d->m_device->sendTrkMessage(TrkInstallFile, TrkCallback(this, &Launcher::handleInstallPackageFinished), ba);
}
+void Launcher::installRemotePackageByUser()
+{
+ emit installingStarted(d->m_installFileNames.at(d->m_currentInstallFileName));
+ d->m_currentInstallationStep = InstallationModeUser;
+ QByteArray ba;
+ appendString(&ba, d->m_installFileNames.at(d->m_currentInstallFileName).toLocal8Bit(), TargetByteOrder, false);
+ d->m_device->sendTrkMessage(TrkInstallFile2, TrkCallback(this, &Launcher::handleInstallPackageFinished), ba);
+}
+
+void Launcher::installRemotePackage()
+{
+ switch (installationMode()) {
+ case InstallationModeSilent:
+ case InstallationModeSilentAndUser:
+ installRemotePackageSilently();
+ break;
+ case InstallationModeUser:
+ installRemotePackageByUser();
+ break;
+ default:
+ break;
+ }
+}
+
void Launcher::handleInstallPackageFinished(const TrkResult &result)
{
if (result.errorCode()) {
- emit canNotInstall(d->m_installFileName, result.errorString());
+ if (installationMode() == InstallationModeSilentAndUser
+ && d->m_currentInstallationStep & InstallationModeSilent) {
+ installRemotePackageByUser();
+ return;
+ }
+ emit canNotInstall(d->m_installFileNames.at(d->m_currentInstallFileName), result.errorString());
disconnectTrk();
return;
- } else {
- emit installingFinished();
}
- if (d->m_startupActions & ActionRun) {
+
+ ++d->m_currentInstallFileName;
+
+ if (d->m_currentInstallFileName == d->m_installFileNames.size())
+ emit installingFinished();
+
+ if (d->m_startupActions & ActionInstall && d->m_currentInstallFileName < d->m_installFileNames.size()) {
+ installRemotePackage();
+ } else if (d->m_startupActions & ActionRun) {
startInferiorIfNeeded();
} else if (d->m_startupActions & ActionDownload) {
copyFileFromRemote();
@@ -884,7 +968,7 @@ void Launcher::handleInstallPackageFinished(const TrkResult &result)
}
QByteArray Launcher::startProcessMessage(const QString &executable,
- const QStringList &arguments)
+ const QString &arguments)
{
// It's not started yet
QByteArray ba;
@@ -894,7 +978,7 @@ QByteArray Launcher::startProcessMessage(const QString &executable,
QByteArray commandLineBa = executable.toLocal8Bit();
commandLineBa.append(char(0));
if (!arguments.isEmpty())
- commandLineBa.append(arguments.join(QString(QLatin1Char(' '))).toLocal8Bit());
+ commandLineBa.append(arguments.toLocal8Bit());
appendString(&ba, commandLineBa, TargetByteOrder, true);
return ba;
}
@@ -930,6 +1014,7 @@ void Launcher::startInferiorIfNeeded()
logMessage("Process already 'started'");
return;
}
+
d->m_device->sendTrkMessage(TrkCreateItem, TrkCallback(this, &Launcher::handleCreateProcess),
startProcessMessage(d->m_fileName, d->m_commandLineArgs)); // Create Item
}
@@ -950,7 +1035,10 @@ Launcher *Launcher::acquireFromDeviceManager(const QString &serverName,
SymbianUtils::SymbianDeviceManager *sdm = SymbianUtils::SymbianDeviceManager::instance();
const QSharedPointer<trk::TrkDevice> device = sdm->acquireDevice(serverName);
if (device.isNull()) {
- *errorMessage = tr("Unable to acquire a device for port '%1'. It appears to be in use.").arg(serverName);
+ if (serverName.isEmpty())
+ *errorMessage = tr("No device is connected. Please connect a device and try again.");
+ else
+ *errorMessage = tr("Unable to acquire a device for port '%1'. It appears to be in use.").arg(serverName);
return 0;
}
// Wire release signal.
@@ -964,6 +1052,8 @@ Launcher *Launcher::acquireFromDeviceManager(const QString &serverName,
// Preliminary release of device, disconnecting the signal.
void Launcher::releaseToDeviceManager(Launcher *launcher)
{
+ Q_ASSERT(launcher);
+
SymbianUtils::SymbianDeviceManager *sdm = SymbianUtils::SymbianDeviceManager::instance();
// Disentangle launcher and its device, remove connection from destroyed
launcher->setCloseDevice(false);
diff --git a/tools/runonphone/symbianutils/launcher.h b/tools/runonphone/symbianutils/launcher.h
index 63189f1..2f674af 100644
--- a/tools/runonphone/symbianutils/launcher.h
+++ b/tools/runonphone/symbianutils/launcher.h
@@ -64,6 +64,13 @@ class SYMBIANUTILS_EXPORT Launcher : public QObject
public:
typedef void (Launcher::*TrkCallBack)(const TrkResult &);
+ enum InstallationMode {
+ InstallationModeSilent = 0x1,
+ InstallationModeUser = 0x2,
+ InstallationModeSilentAndUser = InstallationModeSilent|InstallationModeUser
+ //first attempt is silent and if it fails then the user installation is launched
+ };
+
enum Actions {
ActionPingOnly = 0x0,
ActionCopy = 0x1,
@@ -95,13 +102,19 @@ public:
void setTrkServerName(const QString &name);
QString trkServerName() const;
void setFileName(const QString &name);
- void setCopyFileName(const QString &srcName, const QString &dstName);
+ void setCopyFileNames(const QStringList &srcName, const QStringList &dstName);
void setDownloadFileName(const QString &srcName, const QString &dstName);
- void setInstallFileName(const QString &name);
- void setCommandLineArgs(const QStringList &args);
+ void setInstallFileNames(const QStringList &names);
+ void setCommandLineArgs(const QString &args);
bool startServer(QString *errorMessage);
+ void setInstallationMode(InstallationMode installation);
+ void setInstallationDrive(char drive);
void setVerbose(int v);
void setSerialFrame(bool b);
+
+ InstallationMode installationMode() const;
+ char installationDrive() const;
+
bool serialFrame() const;
// Close device or leave it open
bool closeDevice() const;
@@ -122,7 +135,7 @@ public:
// Create Trk message to start a process.
static QByteArray startProcessMessage(const QString &executable,
- const QStringList &arguments);
+ const QString &arguments);
// Create Trk message to read memory
static QByteArray readMemoryMessage(uint pid, uint tid, uint from, uint len);
static QByteArray readRegistersMessage(uint pid, uint tid);
@@ -135,14 +148,14 @@ public:
signals:
void deviceDescriptionReceived(const QString &port, const QString &description);
- void copyingStarted();
+ void copyingStarted(const QString &fileName);
void canNotConnect(const QString &errorMessage);
void canNotCreateFile(const QString &filename, const QString &errorMessage);
void canNotOpenFile(const QString &filename, const QString &errorMessage);
void canNotOpenLocalFile(const QString &filename, const QString &errorMessage);
void canNotWriteFile(const QString &filename, const QString &errorMessage);
void canNotCloseFile(const QString &filename, const QString &errorMessage);
- void installingStarted();
+ void installingStarted(const QString &packageName);
void canNotInstall(const QString &packageFilename, const QString &errorMessage);
void installingFinished();
void startingApplication();
@@ -152,7 +165,7 @@ signals:
void applicationOutputReceived(const QString &output);
void copyProgress(int percent);
void stateChanged(int);
- void processStopped(uint pc, uint pid, uint tid, const QString& reason);
+ void processStopped(uint pc, uint pid, uint tid, const QString &reason);
void processResumed(uint pid, uint tid);
void libraryLoaded(const trk::Library &lib);
void libraryUnloaded(const trk::Library &lib);
@@ -198,6 +211,8 @@ private:
void copyFileToRemote();
void copyFileFromRemote();
void installRemotePackageSilently();
+ void installRemotePackageByUser();
+ void installRemotePackage();
void startInferiorIfNeeded();
void handleFinished();
diff --git a/tools/runonphone/symbianutils/symbiandevicemanager.cpp b/tools/runonphone/symbianutils/symbiandevicemanager.cpp
index 02727d7..23f5348 100644
--- a/tools/runonphone/symbianutils/symbiandevicemanager.cpp
+++ b/tools/runonphone/symbianutils/symbiandevicemanager.cpp
@@ -41,7 +41,11 @@
#include "symbiandevicemanager.h"
#include "trkdevice.h"
+#include "codadevice.h"
+#include "virtualserialdevice.h"
+#include <QtCore/QCoreApplication>
+#include <QtCore/QEvent>
#include <QtCore/QSettings>
#include <QtCore/QStringList>
#include <QtCore/QFileInfo>
@@ -50,6 +54,9 @@
#include <QtCore/QSharedData>
#include <QtCore/QScopedPointer>
#include <QtCore/QSignalMapper>
+#include <QtCore/QThread>
+#include <QtCore/QWaitCondition>
+#include <QtCore/QTimer>
namespace SymbianUtils {
@@ -66,7 +73,7 @@ public:
SymbianDeviceData();
~SymbianDeviceData();
- inline bool isOpen() const { return !device.isNull() && device->isOpen(); }
+ bool isOpen() const;
void forcedClose();
QString portName;
@@ -77,15 +84,25 @@ public:
DeviceCommunicationType type;
QSharedPointer<trk::TrkDevice> device;
- bool deviceAcquired;
+ QSharedPointer<Coda::CodaDevice> codaDevice;
+ int deviceAcquired;
};
SymbianDeviceData::SymbianDeviceData() :
type(SerialPortCommunication),
- deviceAcquired(false)
+ deviceAcquired(0)
{
}
+bool SymbianDeviceData::isOpen() const
+{
+ if (device)
+ return device->isOpen();
+ if (codaDevice)
+ return codaDevice->device()->isOpen();
+ return false;
+}
+
SymbianDeviceData::~SymbianDeviceData()
{
forcedClose();
@@ -101,7 +118,10 @@ void SymbianDeviceData::forcedClose()
if (deviceAcquired)
qWarning("Device on '%s' unplugged while an operation is in progress.",
qPrintable(portName));
- device->close();
+ if (device)
+ device->close();
+ else
+ codaDevice->device()->close();
}
}
@@ -167,7 +187,7 @@ SymbianDevice::TrkDevicePtr SymbianDevice::acquireDevice()
m_data->device->setPort(m_data->portName);
m_data->device->setSerialFrame(m_data->type == SerialPortCommunication);
}
- m_data->deviceAcquired = true;
+ m_data->deviceAcquired = 1;
return m_data->device;
}
@@ -184,7 +204,7 @@ void SymbianDevice::releaseDevice(TrkDevicePtr *ptr /* = 0 */)
ptr->data()->disconnect();
*ptr = TrkDevicePtr();
}
- m_data->deviceAcquired = false;
+ m_data->deviceAcquired = 0;
} else {
qWarning("Internal error: Attempt to release device that is not acquired.");
}
@@ -255,17 +275,34 @@ SYMBIANUTILS_EXPORT QDebug operator<<(QDebug d, const SymbianDevice &cd)
// ------------- SymbianDeviceManagerPrivate
struct SymbianDeviceManagerPrivate {
- SymbianDeviceManagerPrivate() : m_initialized(false), m_destroyReleaseMapper(0) {}
+ SymbianDeviceManagerPrivate() : m_initialized(false), m_devicesLock(QMutex::Recursive) {}
bool m_initialized;
SymbianDeviceManager::SymbianDeviceList m_devices;
- QSignalMapper *m_destroyReleaseMapper;
+ QMutex m_devicesLock; // Used for protecting access to m_devices and serialising getCodaDevice/delayedClosePort
+ // The following 2 variables are needed to manage requests for a TCF port not coming from the main thread
+ int m_constructTcfPortEventType;
+ QMutex m_codaPortWaitMutex;
+};
+
+class QConstructTcfPortEvent : public QEvent
+{
+public:
+ QConstructTcfPortEvent(QEvent::Type eventId, const QString &portName, CodaDevicePtr *device, QWaitCondition *waiter) :
+ QEvent(eventId), m_portName(portName), m_device(device), m_waiter(waiter)
+ {}
+
+ QString m_portName;
+ CodaDevicePtr* m_device;
+ QWaitCondition *m_waiter;
};
+
SymbianDeviceManager::SymbianDeviceManager(QObject *parent) :
QObject(parent),
d(new SymbianDeviceManagerPrivate)
{
+ d->m_constructTcfPortEventType = QEvent::registerEventType();
}
SymbianDeviceManager::~SymbianDeviceManager()
@@ -276,11 +313,13 @@ SymbianDeviceManager::~SymbianDeviceManager()
SymbianDeviceManager::SymbianDeviceList SymbianDeviceManager::devices() const
{
ensureInitialized();
+ QMutexLocker lock(&d->m_devicesLock);
return d->m_devices;
}
QString SymbianDeviceManager::toString() const
{
+ QMutexLocker lock(&d->m_devicesLock);
QString rc;
QTextStream str(&rc);
str << d->m_devices.size() << " devices:\n";
@@ -305,6 +344,7 @@ int SymbianDeviceManager::findByPortName(const QString &p) const
QString SymbianDeviceManager::friendlyNameForPort(const QString &port) const
{
+ QMutexLocker lock(&d->m_devicesLock);
const int idx = findByPortName(port);
return idx == -1 ? QString() : d->m_devices.at(idx).friendlyName();
}
@@ -326,6 +366,106 @@ SymbianDeviceManager::TrkDevicePtr
return rc;
}
+CodaDevicePtr SymbianDeviceManager::getCodaDevice(const QString &port)
+{
+ ensureInitialized();
+ QMutexLocker lock(&d->m_devicesLock);
+ const int idx = findByPortName(port);
+ if (idx == -1) {
+ qWarning("Attempt to acquire device '%s' that does not exist.", qPrintable(port));
+ if (debug)
+ qDebug() << *this;
+ return CodaDevicePtr();
+ }
+ SymbianDevice& device = d->m_devices[idx];
+ if (device.m_data->device && device.m_data->device.data()->isOpen()) {
+ qWarning("Attempting to open a port '%s' that is configured for TRK!", qPrintable(port));
+ return CodaDevicePtr();
+ }
+ CodaDevicePtr& devicePtr = device.m_data->codaDevice;
+ if (devicePtr.isNull() || !devicePtr->device()->isOpen()) {
+ // Check we instanciate in the correct thread - we can't afford to create the CodaDevice (and more specifically, open the VirtualSerialDevice) in a thread that isn't guaranteed to be long-lived.
+ // Therefore, if we're not in SymbianDeviceManager's thread, rejig things so it's opened in the main thread
+ if (QThread::currentThread() != thread()) {
+ // SymbianDeviceManager is owned by the main thread
+ d->m_codaPortWaitMutex.lock();
+ QWaitCondition waiter;
+ QCoreApplication::postEvent(this, new QConstructTcfPortEvent((QEvent::Type)d->m_constructTcfPortEventType, port, &devicePtr, &waiter));
+ waiter.wait(&d->m_codaPortWaitMutex);
+ // When the wait returns (due to the wakeAll in SymbianDeviceManager::customEvent), the CodaDevice will be fully set up
+ d->m_codaPortWaitMutex.unlock();
+ } else {
+ // We're in the main thread, just set it up directly
+ constructCodaPort(devicePtr, port);
+ }
+ // We still carry on in the case we failed to open so the client can access the IODevice's errorString()
+ }
+ if (devicePtr->device()->isOpen())
+ device.m_data->deviceAcquired++;
+ return devicePtr;
+}
+
+void SymbianDeviceManager::constructCodaPort(CodaDevicePtr& device, const QString& portName)
+{
+ QMutexLocker locker(&d->m_codaPortWaitMutex);
+ if (device.isNull()) {
+ device = QSharedPointer<Coda::CodaDevice>(new Coda::CodaDevice);
+ const QSharedPointer<SymbianUtils::VirtualSerialDevice> serialDevice(new SymbianUtils::VirtualSerialDevice(portName));
+ device->setSerialFrame(true);
+ device->setDevice(serialDevice);
+ }
+ if (!device->device()->isOpen()) {
+ bool ok = device->device().staticCast<SymbianUtils::VirtualSerialDevice>()->open(QIODevice::ReadWrite);
+ if (!ok && debug) {
+ qDebug("SymbianDeviceManager: Failed to open port %s", qPrintable(portName));
+ }
+ }
+}
+
+void SymbianDeviceManager::customEvent(QEvent *event)
+{
+ if (event->type() == d->m_constructTcfPortEventType) {
+ QConstructTcfPortEvent* constructEvent = static_cast<QConstructTcfPortEvent*>(event);
+ constructCodaPort(*constructEvent->m_device, constructEvent->m_portName);
+ constructEvent->m_waiter->wakeAll(); // Should only ever be one thing waiting on this
+ }
+}
+
+void SymbianDeviceManager::releaseCodaDevice(CodaDevicePtr &port)
+{
+ if (port) {
+ QMutexLocker(&d->m_devicesLock);
+ // Check if this was the last reference to the port, if so close it after a short delay
+ foreach (const SymbianDevice& device, d->m_devices) {
+ if (device.m_data->codaDevice.data() == port.data()) {
+ if (device.m_data->deviceAcquired > 0)
+ device.m_data->deviceAcquired--;
+ if (device.m_data->deviceAcquired == 0) {
+ if (debug)
+ qDebug("Starting timer to close port %s", qPrintable(device.m_data->portName));
+ QTimer::singleShot(1000, this, SLOT(delayedClosePort()));
+ }
+ break;
+ }
+ }
+ port.clear();
+ }
+}
+
+void SymbianDeviceManager::delayedClosePort()
+{
+ // Find any coda ports that are still open but have a reference count of zero, and delete them
+ QMutexLocker(&d->m_devicesLock);
+ foreach (const SymbianDevice& device, d->m_devices) {
+ Coda::CodaDevice* codaDevice = device.m_data->codaDevice.data();
+ if (codaDevice && device.m_data->deviceAcquired == 0 && codaDevice->device()->isOpen()) {
+ if (debug)
+ qDebug("Closing device %s", qPrintable(device.m_data->portName));
+ device.m_data->codaDevice->device()->close();
+ }
+ }
+}
+
void SymbianDeviceManager::update()
{
update(true);
@@ -336,11 +476,10 @@ void SymbianDeviceManager::releaseDevice(const QString &port)
const int idx = findByPortName(port);
if (debug)
qDebug() << "SymbianDeviceManager::releaseDevice" << port << idx << sender();
- if (idx != -1) {
+ if (idx != -1)
d->m_devices[idx].releaseDevice();
- } else {
+ else
qWarning("Attempt to release non-existing device %s.", qPrintable(port));
- }
}
void SymbianDeviceManager::setAdditionalInformation(const QString &port, const QString &ai)
@@ -358,6 +497,8 @@ void SymbianDeviceManager::ensureInitialized() const
void SymbianDeviceManager::update(bool emitSignals)
{
+ QMutexLocker lock(&d->m_devicesLock);
+
static int n = 0;
typedef SymbianDeviceList::iterator SymbianDeviceListIterator;
@@ -377,6 +518,7 @@ void SymbianDeviceManager::update(bool emitSignals)
}
// Merge the lists and emit the respective added/removed signals, assuming
// no one can plug a different device on the same port at the speed of lightning
+ SymbianDeviceList removedDevices;
if (!d->m_devices.isEmpty()) {
// Find deleted devices
for (SymbianDeviceListIterator oldIt = d->m_devices.begin(); oldIt != d->m_devices.end(); ) {
@@ -386,25 +528,33 @@ void SymbianDeviceManager::update(bool emitSignals)
SymbianDevice toBeDeleted = *oldIt;
toBeDeleted.forcedClose();
oldIt = d->m_devices.erase(oldIt);
- if (emitSignals)
- emit deviceRemoved(toBeDeleted);
+ removedDevices.append(toBeDeleted);
}
}
}
+ SymbianDeviceList addedDevices;
if (!newDevices.isEmpty()) {
// Find new devices and insert in order
foreach(const SymbianDevice &newDevice, newDevices) {
if (!d->m_devices.contains(newDevice)) {
d->m_devices.append(newDevice);
- if (emitSignals)
- emit deviceAdded(newDevice);
+ addedDevices.append(newDevice);
}
}
if (d->m_devices.size() > 1)
qStableSort(d->m_devices.begin(), d->m_devices.end());
}
- if (emitSignals)
+
+ lock.unlock();
+ if (emitSignals) {
+ foreach (const SymbianDevice &device, removedDevices) {
+ emit deviceRemoved(device);
+ }
+ foreach (const SymbianDevice &device, addedDevices) {
+ emit deviceAdded(device);
+ }
emit updated();
+ }
if (debug)
qDebug("<SerialDeviceLister::update\n%s\n", qPrintable(toString()));
@@ -457,7 +607,9 @@ SymbianDeviceManager::SymbianDeviceList SymbianDeviceManager::blueToothDevices()
}
// New kernel versions support /dev/ttyUSB0, /dev/ttyUSB1. Trk responds
// on the latter (usually), try first.
- static const char *usbTtyDevices[] = { "/dev/ttyUSB1", "/dev/ttyUSB0" };
+ static const char *usbTtyDevices[] = {
+ "/dev/ttyUSB3", "/dev/ttyUSB2", "/dev/ttyUSB1", "/dev/ttyUSB0",
+ "/dev/ttyACM3", "/dev/ttyACM2", "/dev/ttyACM1", "/dev/ttyACM0"};
const int usbTtyCount = sizeof(usbTtyDevices)/sizeof(const char *);
for (int d = 0; d < usbTtyCount; d++) {
const QString ttyUSBDevice = QLatin1String(usbTtyDevices[d]);
@@ -486,4 +638,106 @@ SYMBIANUTILS_EXPORT QDebug operator<<(QDebug d, const SymbianDeviceManager &sdm)
return d;
}
-} // namespace SymbianUtilsInternal
+OstChannel *SymbianDeviceManager::getOstChannel(const QString &port, uchar channelId)
+{
+ CodaDevicePtr coda = getCodaDevice(port);
+ if (coda.isNull() || !coda->device()->isOpen())
+ return 0;
+ return new OstChannel(coda, channelId);
+}
+
+struct OstChannelPrivate
+{
+ CodaDevicePtr m_codaPtr;
+ QByteArray m_dataBuffer;
+ uchar m_channelId;
+ bool m_hasReceivedData;
+};
+
+OstChannel::OstChannel(const CodaDevicePtr &codaPtr, uchar channelId)
+ : d(new OstChannelPrivate)
+{
+ d->m_codaPtr = codaPtr;
+ d->m_channelId = channelId;
+ d->m_hasReceivedData = false;
+ connect(codaPtr.data(), SIGNAL(unknownEvent(uchar, QByteArray)), this, SLOT(ostDataReceived(uchar,QByteArray)));
+ connect(codaPtr->device().data(), SIGNAL(aboutToClose()), this, SLOT(deviceAboutToClose()));
+ QIODevice::open(ReadWrite|Unbuffered);
+}
+
+void OstChannel::close()
+{
+ QIODevice::close();
+ if (d && d->m_codaPtr.data()) {
+ disconnect(d->m_codaPtr.data(), 0, this, 0);
+ SymbianDeviceManager::instance()->releaseCodaDevice(d->m_codaPtr);
+ }
+}
+
+OstChannel::~OstChannel()
+{
+ close();
+ delete d;
+}
+
+void OstChannel::flush()
+{
+ //TODO d->m_codaPtr->device()-
+}
+
+qint64 OstChannel::bytesAvailable() const
+{
+ return d->m_dataBuffer.size();
+}
+
+bool OstChannel::isSequential() const
+{
+ return true;
+}
+
+qint64 OstChannel::readData(char *data, qint64 maxSize)
+{
+ qint64 amount = qMin(maxSize, (qint64)d->m_dataBuffer.size());
+ qMemCopy(data, d->m_dataBuffer.constData(), amount);
+ d->m_dataBuffer.remove(0, amount);
+ return amount;
+}
+
+qint64 OstChannel::writeData(const char *data, qint64 maxSize)
+{
+ static const qint64 KMaxOstPayload = 1024;
+ // If necessary, split the packet up
+ while (maxSize) {
+ QByteArray dataBuf = QByteArray::fromRawData(data, qMin(KMaxOstPayload, maxSize));
+ d->m_codaPtr->writeCustomData(d->m_channelId, dataBuf);
+ data += dataBuf.length();
+ maxSize -= dataBuf.length();
+ }
+ return maxSize;
+}
+
+void OstChannel::ostDataReceived(uchar channelId, const QByteArray &aData)
+{
+ if (channelId == d->m_channelId) {
+ d->m_hasReceivedData = true;
+ d->m_dataBuffer.append(aData);
+ emit readyRead();
+ }
+}
+
+Coda::CodaDevice& OstChannel::codaDevice() const
+{
+ return *d->m_codaPtr;
+}
+
+bool OstChannel::hasReceivedData() const
+{
+ return isOpen() && d->m_hasReceivedData;
+}
+
+void OstChannel::deviceAboutToClose()
+{
+ close();
+}
+
+} // namespace SymbianUtils
diff --git a/tools/runonphone/symbianutils/symbiandevicemanager.h b/tools/runonphone/symbianutils/symbiandevicemanager.h
index d65b5b0..366963c 100644
--- a/tools/runonphone/symbianutils/symbiandevicemanager.h
+++ b/tools/runonphone/symbianutils/symbiandevicemanager.h
@@ -44,7 +44,7 @@
#include "symbianutils_global.h"
-#include <QtCore/QObject>
+#include <QtCore/QIODevice>
#include <QtCore/QExplicitlySharedDataPointer>
#include <QtCore/QSharedPointer>
@@ -56,17 +56,23 @@ QT_END_NAMESPACE
namespace trk {
class TrkDevice;
}
+namespace Coda {
+ class CodaDevice;
+}
namespace SymbianUtils {
struct SymbianDeviceManagerPrivate;
class SymbianDeviceData;
+class OstChannel;
enum DeviceCommunicationType {
SerialPortCommunication = 0,
BlueToothCommunication = 1
};
+typedef QSharedPointer<Coda::CodaDevice> CodaDevicePtr;
+
// SymbianDevice: Explicitly shared device data and a TrkDevice
// instance that can be acquired (exclusively) for use.
// A device removal from the manager will result in the
@@ -90,13 +96,6 @@ public:
QString additionalInformation() const;
void setAdditionalInformation(const QString &);
- // Acquire: Mark the device as 'out' and return a shared pointer
- // unless it is already in use by another owner. The result should not
- // be passed on further.
- TrkDevicePtr acquireDevice();
- // Give back a device and mark it as 'free'.
- void releaseDevice(TrkDevicePtr *ptr = 0);
-
bool isOpen() const;
// Windows only.
@@ -107,6 +106,14 @@ public:
QString toString() const;
private:
+ // Acquire: Mark the device as 'out' and return a shared pointer
+ // unless it is already in use by another owner. The result should not
+ // be passed on further.
+ // TRK only
+ TrkDevicePtr acquireDevice();
+ // Give back a device and mark it as 'free'. TRK only.
+ void releaseDevice(TrkDevicePtr *ptr = 0);
+
void forcedClose();
QExplicitlySharedDataPointer<SymbianDeviceData> m_data;
@@ -145,9 +152,25 @@ public:
SymbianDeviceList devices() const;
QString toString() const;
- // Acquire a device for use. See releaseDevice().
+ // Acquire a TRK device for use. Assuming the port is found, equivalent to devices()[findByPortName(port)].acquireDevice(). See also releaseDevice().
TrkDevicePtr acquireDevice(const QString &port);
+ //// The TCF code prefers to set up the CodaDevice object itself, so we let it and just handle opening the underlying QIODevice and keeping track of the CodaDevice
+ //// Returns true if port was opened successfully.
+
+ // Gets the CodaDevice, which may or may not be open depending on what other clients have already acquired it.
+ // Therefore once clients have set up any signals and slots they required, they should check CodaDevice::device()->isOpen()
+ // and if false, the open failed and they should check device()->errorString() if required.
+ // Caller should call releaseCodaDevice if they want the port to auto-close itself
+ CodaDevicePtr getCodaDevice(const QString &port);
+
+ // Note this function makes no guarantee that someone else isn't already listening on this channel id, or that there is anything on the other end
+ // Returns NULL if the port couldn't be opened
+ OstChannel *getOstChannel(const QString &port, uchar channelId);
+
+ // Caller is responsible for disconnecting any signals from aPort - do not assume the CodaDevice will be deleted as a result of this call. On return aPort will be clear()ed.
+ void releaseCodaDevice(CodaDevicePtr &aPort);
+
int findByPortName(const QString &p) const;
QString friendlyNameForPort(const QString &port) const;
@@ -162,17 +185,54 @@ signals:
void deviceAdded(const SymbianUtils::SymbianDevice &d);
void updated();
+private slots:
+ void delayedClosePort();
+
private:
void ensureInitialized() const;
void update(bool emitSignals);
SymbianDeviceList serialPorts() const;
SymbianDeviceList blueToothDevices() const;
+ void customEvent(QEvent *event);
+ void constructCodaPort(CodaDevicePtr& device, const QString& portName);
SymbianDeviceManagerPrivate *d;
};
SYMBIANUTILS_EXPORT QDebug operator<<(QDebug d, const SymbianDeviceManager &);
+struct OstChannelPrivate;
+
+class SYMBIANUTILS_EXPORT OstChannel : public QIODevice
+{
+ Q_OBJECT
+
+public:
+ void close();
+ ~OstChannel();
+ void flush();
+
+ qint64 bytesAvailable() const;
+ bool isSequential() const;
+ bool hasReceivedData() const;
+
+ Coda::CodaDevice &codaDevice() const;
+
+private slots:
+ void ostDataReceived(uchar channelId, const QByteArray &aData);
+ void deviceAboutToClose();
+
+private:
+ OstChannel(const CodaDevicePtr &codaPtr, uchar channelId);
+ Q_DISABLE_COPY(OstChannel)
+ qint64 readData(char *data, qint64 maxSize);
+ qint64 writeData(const char *data, qint64 maxSize);
+
+private:
+ OstChannelPrivate *d;
+ friend class SymbianDeviceManager;
+};
+
} // namespace SymbianUtils
#endif // SYMBIANDEVICEMANAGER_H
diff --git a/tools/runonphone/symbianutils/symbianutils.pri b/tools/runonphone/symbianutils/symbianutils.pri
index f07e494..2d32680 100644
--- a/tools/runonphone/symbianutils/symbianutils.pri
+++ b/tools/runonphone/symbianutils/symbianutils.pri
@@ -12,9 +12,10 @@ HEADERS += $$PWD/symbianutils_global.h \
$$PWD/bluetoothlistener.h \
$$PWD/communicationstarter.h \
$$PWD/symbiandevicemanager.h \
- $$PWD/tcftrkdevice.h \
- $$PWD/tcftrkmessage.h \
- $$PWD/json.h
+ $$PWD/codadevice.h \
+ $$PWD/codamessage.h \
+ $$PWD/json.h \
+ $$PWD/virtualserialdevice.h
SOURCES += $$PWD/trkutils.cpp \
$$PWD/trkdevice.cpp \
@@ -22,9 +23,14 @@ SOURCES += $$PWD/trkutils.cpp \
$$PWD/bluetoothlistener.cpp \
$$PWD/communicationstarter.cpp \
$$PWD/symbiandevicemanager.cpp \
- $$PWD/tcftrkdevice.cpp \
- $$PWD/tcftrkmessage.cpp \
- $$PWD/json.cpp
+ $$PWD/codadevice.cpp \
+ $$PWD/codamessage.cpp \
+ $$PWD/json.cpp \
+ $$PWD/virtualserialdevice.cpp
+
+DEFINES += HAS_SERIALPORT
+win32:SOURCES += $$PWD/virtualserialdevice_win.cpp
+unix:SOURCES += $$PWD/virtualserialdevice_posix.cpp
# Tests/trklauncher is a console application
contains(QT, gui) {
diff --git a/tools/runonphone/symbianutils/tcftrkdevice.cpp b/tools/runonphone/symbianutils/tcftrkdevice.cpp
deleted file mode 100644
index 219f673..0000000
--- a/tools/runonphone/symbianutils/tcftrkdevice.cpp
+++ /dev/null
@@ -1,929 +0,0 @@
-/****************************************************************************
-**
-** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
-** All rights reserved.
-** Contact: Nokia Corporation (qt-info@nokia.com)
-**
-** This file is part of the tools applications of the Qt Toolkit.
-**
-** $QT_BEGIN_LICENSE:LGPL$
-** GNU Lesser General Public License Usage
-** 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.
-**
-** In addition, as a special exception, Nokia gives you certain additional
-** rights. These rights are described in the Nokia Qt LGPL Exception
-** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
-**
-** GNU General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU General
-** Public License version 3.0 as published by the Free Software Foundation
-** and appearing in the file LICENSE.GPL included in the packaging of this
-** file. Please review the following information to ensure the GNU General
-** Public License version 3.0 requirements will be met:
-** http://www.gnu.org/copyleft/gpl.html.
-**
-** 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.
-**
-**
-**
-**
-**
-** $QT_END_LICENSE$
-**
-****************************************************************************/
-
-#include "tcftrkdevice.h"
-#include "json.h"
-
-#include <QtNetwork/QAbstractSocket>
-#include <QtCore/QDebug>
-#include <QtCore/QVector>
-#include <QtCore/QQueue>
-#include <QtCore/QTextStream>
-#include <QtCore/QDateTime>
-#include <QtCore/QFileInfo>
-
-enum { debug = 0 };
-
-static const char messageTerminatorC[] = "\003\001";
-
-namespace tcftrk {
-// ------------- TcfTrkCommandError
-
-TcfTrkCommandError::TcfTrkCommandError() : timeMS(0), code(0), alternativeCode(0)
-{
-}
-
-void TcfTrkCommandError::clear()
-{
- timeMS = 0;
- code = alternativeCode = 0;
- format.clear();
- alternativeOrganization.clear();
-}
-
-void TcfTrkCommandError::write(QTextStream &str) const
-{
- if (timeMS) {
- const QDateTime time(QDate(1970, 1, 1));
- str << time.addMSecs(timeMS).toString(Qt::ISODate) << ": Error code: " << code
- << " '" << format << '\'';
- if (!alternativeOrganization.isEmpty())
- str << " ('" << alternativeOrganization << "', code: " << alternativeCode << ')';
- } else{
- str << "<No error>";
- }
-}
-
-QString TcfTrkCommandError::toString() const
-{
- QString rc;
- QTextStream str(&rc);
- write(str);
- return rc;
-}
-
-/* {"Time":1277459762255,"Code":1,"AltCode":-6,"AltOrg":"POSIX","Format":"Unknown error: -6"} */
-bool TcfTrkCommandError::parse(const QVector<JsonValue> &values)
-{
- // Parse an arbitrary hash (that could as well be a command response)
- // and check for error elements.
- unsigned errorKeyCount = 0;
- clear();
- do {
- if (values.isEmpty() || values.front().type() != JsonValue::Object)
- break;
- foreach (const JsonValue &c, values.front().children()) {
- if (c.name() == "Time") {
- timeMS = c.data().toULongLong();
- errorKeyCount++;
- } else if (c.name() == "Code") {
- code = c.data().toInt();
- errorKeyCount++;
- } else if (c.name() == "Format") {
- format = c.data();
- errorKeyCount++;
- } else if (c.name() == "AltCode") {
- alternativeCode = c.data().toInt();
- errorKeyCount++;
- } else if (c.name() == "AltOrg") {
- alternativeOrganization = c.data();
- errorKeyCount++;
- }
- }
- } while (false);
- const bool errorFound = errorKeyCount >= 2u; // Should be at least 'Time', 'Code'.
- if (!errorFound)
- clear();
- if (debug) {
- qDebug() << "TcfTrkCommandError::parse: Found error: " << errorFound;
- if (!values.isEmpty())
- qDebug() << values.front().toString();
- }
- return errorFound;
-}
-
-// ------------ TcfTrkCommandResult
-
-TcfTrkCommandResult::TcfTrkCommandResult(Type t) :
- type(t), service(LocatorService)
-{
-}
-
-TcfTrkCommandResult::TcfTrkCommandResult(char typeChar, Services s,
- const QByteArray &r,
- const QVector<JsonValue> &v,
- const QVariant &ck) :
- type(FailReply), service(s), request(r), values(v), cookie(ck)
-{
- switch (typeChar) {
- case 'N':
- type = FailReply;
- break;
- case 'P':
- type = ProgressReply;
- break;
- case 'R':
- type = commandError.parse(values) ? CommandErrorReply : SuccessReply;
- break;
- default:
- qWarning("Unknown TCF reply type '%c'", typeChar);
- }
-}
-
-QString TcfTrkCommandResult::errorString() const
-{
- QString rc;
- QTextStream str(&rc);
-
- switch (type) {
- case SuccessReply:
- case ProgressReply:
- str << "<No error>";
- return rc;
- case FailReply:
- str << "NAK";
- case CommandErrorReply:
- commandError.write(str);
- break;
- }
- // Append the failed command for reference
- str << " (Command was: '";
- QByteArray printableRequest = request;
- printableRequest.replace('\0', '|');
- str << printableRequest << "')";
- return rc;
-}
-
-QString TcfTrkCommandResult::toString() const
-{
- QString rc;
- QTextStream str(&rc);
- str << "Command answer ";
- switch (type) {
- case SuccessReply:
- str << "[success]";
- break;
- case CommandErrorReply:
- str << "[command error]";
- break;
- case FailReply:
- str << "[fail (NAK)]";
- break;
- case ProgressReply:
- str << "[progress]";
- break;
- }
- str << ", " << values.size() << " values(s) to request: '";
- QByteArray printableRequest = request;
- printableRequest.replace('\0', '|');
- str << printableRequest << "' ";
- if (cookie.isValid())
- str << " cookie: " << cookie.toString();
- str << '\n';
- for (int i = 0, count = values.size(); i < count; i++)
- str << '#' << i << ' ' << values.at(i).toString() << '\n';
- if (type == CommandErrorReply)
- str << "Error: " << errorString();
- return rc;
-}
-
-struct TcfTrkSendQueueEntry
-{
- typedef TcfTrkDevice::MessageType MessageType;
-
- explicit TcfTrkSendQueueEntry(MessageType mt,
- int tok,
- Services s,
- const QByteArray &d,
- const TcfTrkCallback &cb= TcfTrkCallback(),
- const QVariant &ck = QVariant()) :
- messageType(mt), service(s), data(d), token(tok), cookie(ck), callback(cb) {}
-
- MessageType messageType;
- Services service;
- QByteArray data;
- int token;
- QVariant cookie;
- TcfTrkCallback callback;
-};
-
-struct TcfTrkDevicePrivate {
- typedef TcfTrkDevice::IODevicePtr IODevicePtr;
- typedef QHash<int, TcfTrkSendQueueEntry> TokenWrittenMessageMap;
-
- TcfTrkDevicePrivate();
-
- const QByteArray m_messageTerminator;
-
- IODevicePtr m_device;
- unsigned m_verbose;
- QByteArray m_readBuffer;
- int m_token;
- QQueue<TcfTrkSendQueueEntry> m_sendQueue;
- TokenWrittenMessageMap m_writtenMessages;
- QVector<QByteArray> m_registerNames;
-};
-
-TcfTrkDevicePrivate::TcfTrkDevicePrivate() :
- m_messageTerminator(messageTerminatorC),
- m_verbose(0), m_token(0)
-{
-}
-
-TcfTrkDevice::TcfTrkDevice(QObject *parent) :
- QObject(parent), d(new TcfTrkDevicePrivate)
-{
-}
-
-TcfTrkDevice::~TcfTrkDevice()
-{
- delete d;
-}
-
-QVector<QByteArray> TcfTrkDevice::registerNames() const
-{
- return d->m_registerNames;
-}
-
-void TcfTrkDevice::setRegisterNames(const QVector<QByteArray>& n)
-{
- d->m_registerNames = n;
- if (d->m_verbose) {
- QString msg;
- QTextStream str(&msg);
- const int count = n.size();
- str << "Registers (" << count << "): ";
- for (int i = 0; i < count; i++)
- str << '#' << i << '=' << n.at(i) << ' ';
- emitLogMessage(msg);
- }
-}
-
-TcfTrkDevice::IODevicePtr TcfTrkDevice::device() const
-{
- return d->m_device;
-}
-
-TcfTrkDevice::IODevicePtr TcfTrkDevice::takeDevice()
-{
- const IODevicePtr old = d->m_device;
- if (!old.isNull()) {
- old.data()->disconnect(this);
- d->m_device = IODevicePtr();
- }
- d->m_readBuffer.clear();
- d->m_token = 0;
- d->m_sendQueue.clear();
- return old;
-}
-
-void TcfTrkDevice::setDevice(const IODevicePtr &dp)
-{
- if (dp.data() == d->m_device.data())
- return;
- if (dp.isNull()) {
- emitLogMessage(QLatin1String("Internal error: Attempt to set NULL device."));
- return;
- }
- takeDevice();
- d->m_device = dp;
- connect(dp.data(), SIGNAL(readyRead()), this, SLOT(slotDeviceReadyRead()));
- if (QAbstractSocket *s = qobject_cast<QAbstractSocket *>(dp.data())) {
- connect(s, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(slotDeviceError()));
- connect(s, SIGNAL(stateChanged(QAbstractSocket::SocketState)), this, SLOT(slotDeviceSocketStateChanged()));
- }
-}
-
-void TcfTrkDevice::slotDeviceError()
-{
- const QString message = d->m_device->errorString();
- emitLogMessage(message);
- emit error(message);
-}
-
-void TcfTrkDevice::slotDeviceSocketStateChanged()
-{
- if (const QAbstractSocket *s = qobject_cast<const QAbstractSocket *>(d->m_device.data())) {
- const QAbstractSocket::SocketState st = s->state();
- switch (st) {
- case QAbstractSocket::UnconnectedState:
- emitLogMessage(QLatin1String("Unconnected"));
- break;
- case QAbstractSocket::HostLookupState:
- emitLogMessage(QLatin1String("HostLookupState"));
- break;
- case QAbstractSocket::ConnectingState:
- emitLogMessage(QLatin1String("Connecting"));
- break;
- case QAbstractSocket::ConnectedState:
- emitLogMessage(QLatin1String("Connected"));
- break;
- case QAbstractSocket::ClosingState:
- emitLogMessage(QLatin1String("Closing"));
- break;
- default:
- emitLogMessage(QString::fromLatin1("State %1").arg(st));
- break;
- }
- }
-}
-
-static inline QString debugMessage(QByteArray message, const char *prefix = 0)
-{
- message.replace('\0', '|');
- const QString messageS = QString::fromLatin1(message);
- return prefix ?
- (QLatin1String(prefix) + messageS) : messageS;
-}
-
-void TcfTrkDevice::slotDeviceReadyRead()
-{
- d->m_readBuffer += d->m_device->readAll();
- // Take complete message off front of readbuffer.
- do {
- const int messageEndPos = d->m_readBuffer.indexOf(d->m_messageTerminator);
- if (messageEndPos == -1)
- break;
- const QByteArray message = d->m_readBuffer.left(messageEndPos);
- if (debug)
- qDebug("Read:\n%s", qPrintable(formatData(message)));
- if (const int errorCode = parseMessage(message)) {
- emitLogMessage(QString::fromLatin1("Parse error %1 for: %2").arg(errorCode).arg(debugMessage(message)));
- }
- d->m_readBuffer.remove(0, messageEndPos + d->m_messageTerminator.size());
- } while (!d->m_readBuffer.isEmpty());
- checkSendQueue(); // Send off further message
-}
-
-// Split \0-terminated message into tokens, skipping the initial type character
-static inline QVector<QByteArray> splitMessage(const QByteArray &message)
-{
- QVector<QByteArray> tokens;
- tokens.reserve(7);
- const int messageSize = message.size();
- for (int pos = 2; pos < messageSize; ) {
- const int nextPos = message.indexOf('\0', pos);
- if (nextPos == -1)
- break;
- tokens.push_back(message.mid(pos, nextPos - pos));
- pos = nextPos + 1;
- }
- return tokens;
-}
-
-int TcfTrkDevice::parseMessage(const QByteArray &message)
-{
- if (d->m_verbose)
- emitLogMessage(debugMessage(message, "TCF ->"));
- // Special JSON parse error message or protocol format error.
- // The port is usually closed after receiving it.
- // "\3\2{"Time":1276096098255,"Code":3,"Format": "Protocol format error"}"
- if (message.startsWith("\003\002")) {
- QByteArray text = message.mid(2);
- const QString errorMessage = QString::fromLatin1("Parse error received: %1").arg(QString::fromAscii(text));
- emit error(errorMessage);
- return 0;
- }
- if (message.size() < 4 || message.at(1) != '\0')
- return 1;
- // Split into tokens
- const char type = message.at(0);
- const QVector<QByteArray> tokens = splitMessage(message);
- switch (type) {
- case 'E':
- return parseTcfEvent(tokens);
- case 'R': // Command replies
- case 'N':
- case 'P':
- return parseTcfCommandReply(type, tokens);
- default:
- emitLogMessage(QString::fromLatin1("Unhandled message type: %1").arg(debugMessage(message)));
- return 756;
- }
- return 0;
-}
-
-int TcfTrkDevice::parseTcfCommandReply(char type, const QVector<QByteArray> &tokens)
-{
- typedef TcfTrkDevicePrivate::TokenWrittenMessageMap::iterator TokenWrittenMessageMapIterator;
- // Find the corresponding entry in the written messages hash.
- const int tokenCount = tokens.size();
- if (tokenCount < 1)
- return 234;
- bool tokenOk;
- const int token = tokens.at(0).toInt(&tokenOk);
- if (!tokenOk)
- return 235;
- const TokenWrittenMessageMapIterator it = d->m_writtenMessages.find(token);
- if (it == d->m_writtenMessages.end()) {
- qWarning("TcfTrkDevice: Internal error: token %d not found for '%s'",
- token, qPrintable(joinByteArrays(tokens)));
- return 236;
- }
- // No callback: remove entry from map, happy
- if (!it.value().callback) {
- d->m_writtenMessages.erase(it);
- return 0;
- }
- // Parse values into JSON
- QVector<JsonValue> values;
- values.reserve(tokenCount);
- for (int i = 1; i < tokenCount; i++) {
- if (!tokens.at(i).isEmpty()) { // Strange: Empty tokens occur.
- const JsonValue value(tokens.at(i));
- if (value.isValid()) {
- values.push_back(value);
- } else {
- qWarning("JSON parse error for reply to command token %d: #%d '%s'",
- token, i, tokens.at(i).constData());
- d->m_writtenMessages.erase(it);
- return -1;
- }
- }
- }
-
- // Construct result and invoke callback, remove entry from map.
- TcfTrkCallback callback = it.value().callback;
- TcfTrkCommandResult result(type, it.value().service, it.value().data,
- values, it.value().cookie);
- d->m_writtenMessages.erase(it);
- callback(result);
- return 0;
-}
-
-static const char locatorAnswerC[] = "E\0Locator\0Hello\0[\"Locator\"]";
-
-int TcfTrkDevice::parseTcfEvent(const QVector<QByteArray> &tokens)
-{
- // Event: Ignore the periodical heartbeat event, answer 'Hello',
- // emit signal for the rest
- if (tokens.size() < 3)
- return 433;
- const Services service = serviceFromName(tokens.at(0).constData());
- if (service == LocatorService && tokens.at(1) == "peerHeartBeat")
- return 0;
- QVector<JsonValue> values;
- for (int i = 2; i < tokens.size(); i++) {
- const JsonValue value(tokens.at(i));
- if (!value.isValid())
- return 434;
- values.push_back(value);
- }
- // Parse known events, emit signals
- QScopedPointer<TcfTrkEvent> knownEvent(TcfTrkEvent::parseEvent(service, tokens.at(1), values));
- if (!knownEvent.isNull()) {
- // Answer hello event.
- if (knownEvent->type() == TcfTrkEvent::LocatorHello)
- writeMessage(QByteArray(locatorAnswerC, sizeof(locatorAnswerC)));
- emit tcfEvent(*knownEvent);
- }
- emit genericTcfEvent(service, tokens.at(1), values);
-
- if (debug || d->m_verbose) {
- QString msg;
- QTextStream str(&msg);
- if (knownEvent.isNull()) {
- str << "Event: " << tokens.at(0) << ' ' << tokens.at(1) << '\n';
- foreach(const JsonValue &val, values)
- str << " " << val.toString() << '\n';
- } else {
- str << knownEvent->toString();
- }
- emitLogMessage(msg);
- }
-
- return 0;
-}
-
-unsigned TcfTrkDevice::verbose() const
-{
- return d->m_verbose;
-}
-
-void TcfTrkDevice::setVerbose(unsigned v)
-{
- d->m_verbose = v;
-}
-
-void TcfTrkDevice::emitLogMessage(const QString &m)
-{
- if (debug)
- qWarning("%s", qPrintable(m));
- emit logMessage(m);
-}
-
-bool TcfTrkDevice::checkOpen()
-{
- if (d->m_device.isNull()) {
- emitLogMessage(QLatin1String("Internal error: No device set on TcfTrkDevice."));
- return false;
- }
- if (!d->m_device->isOpen()) {
- emitLogMessage(QLatin1String("Internal error: Device not open in TcfTrkDevice."));
- return false;
- }
- return true;
-}
-
-void TcfTrkDevice::sendTcfTrkMessage(MessageType mt, Services service, const char *command,
- const char *commandParameters, int commandParametersLength,
- const TcfTrkCallback &callBack,
- const QVariant &cookie)
-
-{
- if (!checkOpen())
- return;
- // Format the message
- const int token = d->m_token++;
- QByteArray data;
- data.reserve(30 + commandParametersLength);
- data.append('C');
- data.append('\0');
- data.append(QByteArray::number(token));
- data.append('\0');
- data.append(serviceName(service));
- data.append('\0');
- data.append(command);
- data.append('\0');
- if (commandParametersLength)
- data.append(commandParameters, commandParametersLength);
- const TcfTrkSendQueueEntry entry(mt, token, service, data, callBack, cookie);
- d->m_sendQueue.enqueue(entry);
- checkSendQueue();
-}
-
-void TcfTrkDevice::sendTcfTrkMessage(MessageType mt, Services service, const char *command,
- const QByteArray &commandParameters,
- const TcfTrkCallback &callBack,
- const QVariant &cookie)
-{
- sendTcfTrkMessage(mt, service, command, commandParameters.constData(), commandParameters.size(),
- callBack, cookie);
-}
-
-// Enclose in message frame and write.
-void TcfTrkDevice::writeMessage(QByteArray data)
-{
- if (!checkOpen())
- return;
-
- if (d->m_verbose)
- emitLogMessage(debugMessage(data, "TCF <-"));
-
- // Ensure \0-termination which easily gets lost in QByteArray CT.
- if (!data.endsWith('\0'))
- data.append('\0');
- data += d->m_messageTerminator;
-
- if (debug > 1)
- qDebug("Writing:\n%s", qPrintable(formatData(data)));
-
- d->m_device->write(data);
- if (QAbstractSocket *as = qobject_cast<QAbstractSocket *>(d->m_device.data()))
- as->flush();
-}
-
-void TcfTrkDevice::checkSendQueue()
-{
- // Fire off messages or invoke noops until a message with reply is found
- // and an entry to writtenMessages is made.
- while (d->m_writtenMessages.empty()) {
- if (d->m_sendQueue.isEmpty())
- break;
- TcfTrkSendQueueEntry entry = d->m_sendQueue.dequeue();
- switch (entry.messageType) {
- case MessageWithReply:
- d->m_writtenMessages.insert(entry.token, entry);
- writeMessage(entry.data);
- break;
- case MessageWithoutReply:
- writeMessage(entry.data);
- break;
- case NoopMessage: // Invoke the noop-callback for synchronization
- if (entry.callback) {
- TcfTrkCommandResult noopResult(TcfTrkCommandResult::SuccessReply);
- noopResult.cookie = entry.cookie;
- entry.callback(noopResult);
- }
- break;
- }
- }
-}
-
-// Fix slashes
-static inline QString fixFileName(QString in)
-{
- in.replace(QLatin1Char('/'), QLatin1Char('\\'));
- return in;
-}
-
-// Start a process (consisting of a non-reply setSettings and start).
-void TcfTrkDevice::sendProcessStartCommand(const TcfTrkCallback &callBack,
- const QString &binaryIn,
- unsigned uid,
- QStringList arguments,
- QString workingDirectory,
- bool debugControl,
- const QStringList &additionalLibraries,
- const QVariant &cookie)
-{
- // Obtain the bin directory, expand by c:/sys/bin if missing
- const QChar backSlash('\\');
- int slashPos = binaryIn.lastIndexOf(QLatin1Char('/'));
- if (slashPos == -1)
- slashPos = binaryIn.lastIndexOf(backSlash);
- const QString sysBin = QLatin1String("c:/sys/bin");
- const QString binaryFileName = slashPos == -1 ? binaryIn : binaryIn.mid(slashPos + 1);
- const QString binaryDirectory = slashPos == -1 ? sysBin : binaryIn.left(slashPos);
- const QString binary = fixFileName(binaryDirectory + QLatin1Char('/') + binaryFileName);
-
- // Fixup: Does argv[0] convention exist on Symbian?
- arguments.push_front(binary);
- if (workingDirectory.isEmpty())
- workingDirectory = sysBin;
-
- // Format settings with empty dummy parameter
- QByteArray setData;
- JsonInputStream setStr(setData);
- setStr << "" << '\0'
- << '[' << "exeToLaunch" << ',' << "addExecutables" << ',' << "addLibraries" << ']'
- << '\0' << '['
- << binary << ','
- << '{' << binaryFileName << ':' << QString::number(uid, 16) << '}' << ','
- << additionalLibraries
- << ']';
- sendTcfTrkMessage(MessageWithoutReply, SettingsService, "set", setData);
-
- QByteArray startData;
- JsonInputStream startStr(startData);
- startStr << fixFileName(workingDirectory)
- << '\0' << binary << '\0' << arguments << '\0'
- << QStringList() << '\0' // Env is an array ["PATH=value"] (non-standard)
- << debugControl;
- sendTcfTrkMessage(MessageWithReply, ProcessesService, "start", startData, callBack, cookie);
-}
-
-void TcfTrkDevice::sendProcessTerminateCommand(const TcfTrkCallback &callBack,
- const QByteArray &id,
- const QVariant &cookie)
-{
- QByteArray data;
- JsonInputStream str(data);
- str << id;
- sendTcfTrkMessage(MessageWithReply, ProcessesService, "terminate", data, callBack, cookie);
-}
-
-void TcfTrkDevice::sendRunControlTerminateCommand(const TcfTrkCallback &callBack,
- const QByteArray &id,
- const QVariant &cookie)
-{
- QByteArray data;
- JsonInputStream str(data);
- str << id;
- sendTcfTrkMessage(MessageWithReply, RunControlService, "terminate", data, callBack, cookie);
-}
-
-// Non-standard: Remove executable from settings
-void TcfTrkDevice::sendSettingsRemoveExecutableCommand(const QString &binaryIn,
- unsigned uid,
- const QStringList &additionalLibraries,
- const QVariant &cookie)
-{
- QByteArray setData;
- JsonInputStream setStr(setData);
- setStr << "" << '\0'
- << '[' << "removedExecutables" << ',' << "removedLibraries" << ']'
- << '\0' << '['
- << '{' << QFileInfo(binaryIn).fileName() << ':' << QString::number(uid, 16) << '}' << ','
- << additionalLibraries
- << ']';
- sendTcfTrkMessage(MessageWithoutReply, SettingsService, "set", setData, TcfTrkCallback(), cookie);
-}
-
-void TcfTrkDevice::sendRunControlResumeCommand(const TcfTrkCallback &callBack,
- const QByteArray &id,
- RunControlResumeMode mode,
- unsigned count,
- quint64 rangeStart,
- quint64 rangeEnd,
- const QVariant &cookie)
-{
- QByteArray resumeData;
- JsonInputStream str(resumeData);
- str << id << '\0' << int(mode) << '\0' << count;
- switch (mode) {
- case RM_STEP_OVER_RANGE:
- case RM_STEP_INTO_RANGE:
- case RM_REVERSE_STEP_OVER_RANGE:
- case RM_REVERSE_STEP_INTO_RANGE:
- str << '\0' << '{' << "RANGE_START" << ':' << rangeStart
- << ',' << "RANGE_END" << ':' << rangeEnd << '}';
- break;
- default:
- break;
- }
- sendTcfTrkMessage(MessageWithReply, RunControlService, "resume", resumeData, callBack, cookie);
-}
-
-void TcfTrkDevice::sendRunControlSuspendCommand(const TcfTrkCallback &callBack,
- const QByteArray &id,
- const QVariant &cookie)
-{
- QByteArray data;
- JsonInputStream str(data);
- str << id;
- sendTcfTrkMessage(MessageWithReply, RunControlService, "suspend", data, callBack, cookie);
-}
-
-void TcfTrkDevice::sendRunControlResumeCommand(const TcfTrkCallback &callBack,
- const QByteArray &id,
- const QVariant &cookie)
-{
- sendRunControlResumeCommand(callBack, id, RM_RESUME, 1, 0, 0, cookie);
-}
-
-void TcfTrkDevice::sendBreakpointsAddCommand(const TcfTrkCallback &callBack,
- const Breakpoint &bp,
- const QVariant &cookie)
-{
- QByteArray data;
- JsonInputStream str(data);
- str << bp;
- sendTcfTrkMessage(MessageWithReply, BreakpointsService, "add", data, callBack, cookie);
-}
-
-void TcfTrkDevice::sendBreakpointsRemoveCommand(const TcfTrkCallback &callBack,
- const QByteArray &id,
- const QVariant &cookie)
-{
- sendBreakpointsRemoveCommand(callBack, QVector<QByteArray>(1, id), cookie);
-}
-
-void TcfTrkDevice::sendBreakpointsRemoveCommand(const TcfTrkCallback &callBack,
- const QVector<QByteArray> &ids,
- const QVariant &cookie)
-{
- QByteArray data;
- JsonInputStream str(data);
- str << ids;
- sendTcfTrkMessage(MessageWithReply, BreakpointsService, "remove", data, callBack, cookie);
-}
-
-void TcfTrkDevice::sendBreakpointsEnableCommand(const TcfTrkCallback &callBack,
- const QByteArray &id,
- bool enable,
- const QVariant &cookie)
-{
- sendBreakpointsEnableCommand(callBack, QVector<QByteArray>(1, id), enable, cookie);
-}
-
-void TcfTrkDevice::sendBreakpointsEnableCommand(const TcfTrkCallback &callBack,
- const QVector<QByteArray> &ids,
- bool enable,
- const QVariant &cookie)
-{
- QByteArray data;
- JsonInputStream str(data);
- str << ids;
- sendTcfTrkMessage(MessageWithReply, BreakpointsService,
- enable ? "enable" : "disable",
- data, callBack, cookie);
-}
-
-void TcfTrkDevice::sendMemorySetCommand(const TcfTrkCallback &callBack,
- const QByteArray &contextId,
- quint64 start, const QByteArray& data,
- const QVariant &cookie)
-{
- QByteArray getData;
- JsonInputStream str(getData);
- // start/word size/mode. Mode should ideally be 1 (continue on error?)
- str << contextId << '\0' << start << '\0' << 1 << '\0' << data.size() << '\0' << 1
- << '\0' << data.toBase64();
- sendTcfTrkMessage(MessageWithReply, MemoryService, "set", getData, callBack, cookie);
-}
-
-void TcfTrkDevice::sendMemoryGetCommand(const TcfTrkCallback &callBack,
- const QByteArray &contextId,
- quint64 start, quint64 size,
- const QVariant &cookie)
-{
- QByteArray data;
- JsonInputStream str(data);
- // start/word size/mode. Mode should ideally be 1 (continue on error?)
- str << contextId << '\0' << start << '\0' << 1 << '\0' << size << '\0' << 1;
- sendTcfTrkMessage(MessageWithReply, MemoryService, "get", data, callBack, cookie);
-}
-
-QByteArray TcfTrkDevice::parseMemoryGet(const TcfTrkCommandResult &r)
-{
- if (r.type != TcfTrkCommandResult::SuccessReply || r.values.size() < 1)
- return QByteArray();
- const JsonValue &memoryV = r.values.front();
-
- if (memoryV.type() != JsonValue::String || memoryV.data().size() < 2
- || !memoryV.data().endsWith('='))
- return QByteArray();
- // Catch errors reported as hash:
- // R.4."TlVMTA==".{"Time":1276786871255,"Code":1,"AltCode":-38,"AltOrg":"POSIX","Format":"BadDescriptor"}
- // Not sure what to make of it.
- if (r.values.size() >= 2 && r.values.at(1).type() == JsonValue::Object)
- qWarning("Error retrieving memory: %s", r.values.at(1).toString(false).constData());
- // decode
- const QByteArray memory = QByteArray::fromBase64(memoryV.data());
- if (memory.isEmpty())
- qWarning("Base64 decoding of %s failed.", memoryV.data().constData());
- if (debug)
- qDebug("TcfTrkDevice::parseMemoryGet: received %d bytes", memory.size());
- return memory;
-}
-
-void TcfTrkDevice::sendRegistersGetMCommand(const TcfTrkCallback &callBack,
- const QByteArray &contextId,
- const QVector<QByteArray> &ids,
- const QVariant &cookie)
-{
- // TODO: use "Registers" (which uses base64-encoded values)
- QByteArray data;
- JsonInputStream str(data);
- str << contextId << '\0' << ids;
- sendTcfTrkMessage(MessageWithReply, SimpleRegistersService, "get", data, callBack, cookie);
-}
-
-void TcfTrkDevice::sendRegistersGetMRangeCommand(const TcfTrkCallback &callBack,
- const QByteArray &contextId,
- unsigned start, unsigned count)
-{
- const unsigned end = start + count;
- if (end > (unsigned)d->m_registerNames.size()) {
- qWarning("TcfTrkDevice: No register name set for index %u (size: %d).", end, d->m_registerNames.size());
- return;
- }
-
- QVector<QByteArray> ids;
- ids.reserve(count);
- for (unsigned i = start; i < end; i++)
- ids.push_back(d->m_registerNames.at(i));
- sendRegistersGetMCommand(callBack, contextId, ids, QVariant(start));
-}
-
-// Set register
-void TcfTrkDevice::sendRegistersSetCommand(const TcfTrkCallback &callBack,
- const QByteArray &contextId,
- const QByteArray &id,
- unsigned value,
- const QVariant &cookie)
-{
- // TODO: use "Registers" (which uses base64-encoded values)
- QByteArray data;
- JsonInputStream str(data);
- str << contextId << '\0' << QVector<QByteArray>(1, id)
- << '\0' << QVector<QByteArray>(1, QByteArray::number(value, 16));
- sendTcfTrkMessage(MessageWithReply, SimpleRegistersService, "set", data, callBack, cookie);
-}
-
-// Set register
-void TcfTrkDevice::sendRegistersSetCommand(const TcfTrkCallback &callBack,
- const QByteArray &contextId,
- unsigned registerNumber,
- unsigned value,
- const QVariant &cookie)
-{
- if (registerNumber >= (unsigned)d->m_registerNames.size()) {
- qWarning("TcfTrkDevice: No register name set for index %u (size: %d).", registerNumber, d->m_registerNames.size());
- return;
- }
- sendRegistersSetCommand(callBack, contextId,
- d->m_registerNames[registerNumber],
- value, cookie);
-}
-
-} // namespace tcftrk
diff --git a/tools/runonphone/symbianutils/tcftrkdevice.h b/tools/runonphone/symbianutils/tcftrkdevice.h
deleted file mode 100644
index 8f754e8..0000000
--- a/tools/runonphone/symbianutils/tcftrkdevice.h
+++ /dev/null
@@ -1,295 +0,0 @@
-/****************************************************************************
-**
-** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
-** All rights reserved.
-** Contact: Nokia Corporation (qt-info@nokia.com)
-**
-** This file is part of the tools applications of the Qt Toolkit.
-**
-** $QT_BEGIN_LICENSE:LGPL$
-** GNU Lesser General Public License Usage
-** 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.
-**
-** In addition, as a special exception, Nokia gives you certain additional
-** rights. These rights are described in the Nokia Qt LGPL Exception
-** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
-**
-** GNU General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU General
-** Public License version 3.0 as published by the Free Software Foundation
-** and appearing in the file LICENSE.GPL included in the packaging of this
-** file. Please review the following information to ensure the GNU General
-** Public License version 3.0 requirements will be met:
-** http://www.gnu.org/copyleft/gpl.html.
-**
-** 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.
-**
-**
-**
-**
-**
-** $QT_END_LICENSE$
-**
-****************************************************************************/
-
-#ifndef TCFTRKENGINE_H
-#define TCFTRKENGINE_H
-
-#include "symbianutils_global.h"
-#include "tcftrkmessage.h"
-#include "callback.h"
-#include "json.h"
-
-#include <QtCore/QObject>
-#include <QtCore/QSharedPointer>
-#include <QtCore/QVector>
-#include <QtCore/QVariant>
-#include <QtCore/QStringList>
-
-QT_BEGIN_NAMESPACE
-class QIODevice;
-class QTextStream;
-QT_END_NAMESPACE
-
-namespace tcftrk {
-
-struct TcfTrkDevicePrivate;
-struct Breakpoint;
-
-/* Command error handling in TCF:
- * 1) 'Severe' errors (JSON format, parameter format): Trk emits a
- * nonstandard message (\3\2 error parameters) and closes the connection.
- * 2) Protocol errors: 'N' without error message is returned.
- * 3) Errors in command execution: 'R' with a TCF error hash is returned
- * (see TcfTrkCommandError). */
-
-/* Error code return in 'R' reply to command
- * (see top of 'Services' documentation). */
-struct SYMBIANUTILS_EXPORT TcfTrkCommandError {
- TcfTrkCommandError();
- void clear();
- operator bool() const { return timeMS != 0; }
- QString toString() const;
- void write(QTextStream &str) const;
- bool parse(const QVector<JsonValue> &values);
-
- quint64 timeMS; // Since 1.1.1970
- int code;
- QByteArray format; // message
- // 'Alternative' meaning, like altOrg="POSIX"/altCode=<some errno>
- QByteArray alternativeOrganization;
- int alternativeCode;
-};
-
-/* Answer to a Tcf command passed to the callback. */
-struct SYMBIANUTILS_EXPORT TcfTrkCommandResult {
- enum Type {
- SuccessReply, // 'R' and no error -> all happy.
- CommandErrorReply, // 'R' with TcfTrkCommandError received
- ProgressReply, // 'P', progress indicator
- FailReply // 'N' Protocol NAK, severe error
- };
-
- explicit TcfTrkCommandResult(Type t = SuccessReply);
- explicit TcfTrkCommandResult(char typeChar, Services service,
- const QByteArray &request,
- const QVector<JsonValue> &values,
- const QVariant &cookie);
-
- QString toString() const;
- QString errorString() const;
- operator bool() const { return type == SuccessReply || type == ProgressReply; }
-
- Type type;
- Services service;
- QByteArray request;
- TcfTrkCommandError commandError;
- QVector<JsonValue> values;
- QVariant cookie;
-};
-
-typedef trk::Callback<const TcfTrkCommandResult &> TcfTrkCallback;
-
-/* TcfTrkDevice: TCF communication helper using an asynchronous QIODevice
- * implementing the TCF protocol according to:
-http://dev.eclipse.org/svnroot/dsdp/org.eclipse.tm.tcf/trunk/docs/TCF%20Specification.html
-http://dev.eclipse.org/svnroot/dsdp/org.eclipse.tm.tcf/trunk/docs/TCF%20Services.html
- * Commands can be sent along with callbacks that are passed a
- * TcfTrkCommandResult and an opaque QVariant cookie. In addition, events are emitted.
-*/
-
-class SYMBIANUTILS_EXPORT TcfTrkDevice : public QObject
-{
- Q_PROPERTY(unsigned verbose READ verbose WRITE setVerbose)
- Q_OBJECT
-public:
- enum MessageType { MessageWithReply,
- MessageWithoutReply, /* Non-standard: "Settings:set" command does not reply */
- NoopMessage };
-
- typedef QSharedPointer<QIODevice> IODevicePtr;
-
- explicit TcfTrkDevice(QObject *parent = 0);
- virtual ~TcfTrkDevice();
-
- unsigned verbose() const;
-
- // Mapping of register names for indices
- QVector<QByteArray> registerNames() const;
- void setRegisterNames(const QVector<QByteArray>& n);
-
- IODevicePtr device() const;
- IODevicePtr takeDevice();
- void setDevice(const IODevicePtr &dp);
-
- void sendTcfTrkMessage(MessageType mt, Services service,
- const char *command,
- const char *commandParameters, int commandParametersLength,
- const TcfTrkCallback &callBack = TcfTrkCallback(),
- const QVariant &cookie = QVariant());
-
- void sendTcfTrkMessage(MessageType mt, Services service, const char *command,
- const QByteArray &commandParameters,
- const TcfTrkCallback &callBack = TcfTrkCallback(),
- const QVariant &cookie = QVariant());
-
- // Convenience messages: Start a process
- void sendProcessStartCommand(const TcfTrkCallback &callBack,
- const QString &binary,
- unsigned uid,
- QStringList arguments = QStringList(),
- QString workingDirectory = QString(),
- bool debugControl = true,
- const QStringList &additionalLibraries = QStringList(),
- const QVariant &cookie = QVariant());
-
- // Preferred over Processes:Terminate by TCF TRK.
- void sendRunControlTerminateCommand(const TcfTrkCallback &callBack,
- const QByteArray &id,
- const QVariant &cookie = QVariant());
-
- void sendProcessTerminateCommand(const TcfTrkCallback &callBack,
- const QByteArray &id,
- const QVariant &cookie = QVariant());
-
- // Non-standard: Remove executable from settings.
- // Probably needs to be called after stopping. This command has no response.
- void sendSettingsRemoveExecutableCommand(const QString &binaryIn,
- unsigned uid,
- const QStringList &additionalLibraries = QStringList(),
- const QVariant &cookie = QVariant());
-
- void sendRunControlSuspendCommand(const TcfTrkCallback &callBack,
- const QByteArray &id,
- const QVariant &cookie = QVariant());
-
- // Resume / Step (see RunControlResumeMode).
- void sendRunControlResumeCommand(const TcfTrkCallback &callBack,
- const QByteArray &id,
- RunControlResumeMode mode,
- unsigned count /* = 1, currently ignored. */,
- quint64 rangeStart, quint64 rangeEnd,
- const QVariant &cookie = QVariant());
-
- // Convenience to resume a suspended process
- void sendRunControlResumeCommand(const TcfTrkCallback &callBack,
- const QByteArray &id,
- const QVariant &cookie = QVariant());
-
- void sendBreakpointsAddCommand(const TcfTrkCallback &callBack,
- const Breakpoint &b,
- const QVariant &cookie = QVariant());
-
- void sendBreakpointsRemoveCommand(const TcfTrkCallback &callBack,
- const QByteArray &id,
- const QVariant &cookie = QVariant());
-
- void sendBreakpointsRemoveCommand(const TcfTrkCallback &callBack,
- const QVector<QByteArray> &id,
- const QVariant &cookie = QVariant());
-
- void sendBreakpointsEnableCommand(const TcfTrkCallback &callBack,
- const QByteArray &id,
- bool enable,
- const QVariant &cookie = QVariant());
-
- void sendBreakpointsEnableCommand(const TcfTrkCallback &callBack,
- const QVector<QByteArray> &id,
- bool enable,
- const QVariant &cookie = QVariant());
-
-
- void sendMemoryGetCommand(const TcfTrkCallback &callBack,
- const QByteArray &contextId,
- quint64 start, quint64 size,
- const QVariant &cookie = QVariant());
-
- void sendMemorySetCommand(const TcfTrkCallback &callBack,
- const QByteArray &contextId,
- quint64 start, const QByteArray& data,
- const QVariant &cookie = QVariant());
-
- // Reply is an array of hexvalues
- void sendRegistersGetMCommand(const TcfTrkCallback &callBack,
- const QByteArray &contextId,
- const QVector<QByteArray> &ids,
- const QVariant &cookie = QVariant());
-
- // Convenience to get a range of register "R0" .. "R<n>".
- // Cookie will be an int containing "start".
- void sendRegistersGetMRangeCommand(const TcfTrkCallback &callBack,
- const QByteArray &contextId,
- unsigned start, unsigned count);
-
- // Set register
- void sendRegistersSetCommand(const TcfTrkCallback &callBack,
- const QByteArray &contextId,
- const QByteArray &ids,
- unsigned value,
- const QVariant &cookie = QVariant());
- // Set register
- void sendRegistersSetCommand(const TcfTrkCallback &callBack,
- const QByteArray &contextId,
- unsigned registerNumber,
- unsigned value,
- const QVariant &cookie = QVariant());
-
- static QByteArray parseMemoryGet(const TcfTrkCommandResult &r);
-
-signals:
- void genericTcfEvent(int service, const QByteArray &name, const QVector<tcftrk::JsonValue> &value);
- void tcfEvent(const tcftrk::TcfTrkEvent &knownEvent);
-
- void logMessage(const QString &);
- void error(const QString &);
-
-public slots:
- void setVerbose(unsigned v);
-
-private slots:
- void slotDeviceError();
- void slotDeviceSocketStateChanged();
- void slotDeviceReadyRead();
-
-private:
- bool checkOpen();
- void checkSendQueue();
- void writeMessage(QByteArray data);
- void emitLogMessage(const QString &);
- int parseMessage(const QByteArray &);
- int parseTcfCommandReply(char type, const QVector<QByteArray> &tokens);
- int parseTcfEvent(const QVector<QByteArray> &tokens);
-
- TcfTrkDevicePrivate *d;
-};
-
-} // namespace tcftrk
-
-#endif // TCFTRKENGINE_H
diff --git a/tools/runonphone/symbianutils/trkutils.h b/tools/runonphone/symbianutils/trkutils.h
index 663dc38..a4f5453 100644
--- a/tools/runonphone/symbianutils/trkutils.h
+++ b/tools/runonphone/symbianutils/trkutils.h
@@ -145,7 +145,7 @@ enum DSOSItemTypes {
kDSOSProcAttachItem = 0x0005,
kDSOSThreadAttachItem = 0x0006,
kDSOSProcAttach2Item = 0x0007,
- kDSOSProcRunItem = 0x0008,
+ kDSOSProcRunItem = 0x0008
/* 0x0009 - 0x00ff reserved for general expansion */
/* 0x0100 - 0xffff available for target-specific use */
};
@@ -170,7 +170,7 @@ enum Endianness
{
LittleEndian,
BigEndian,
- TargetByteOrder = BigEndian,
+ TargetByteOrder = BigEndian
};
SYMBIANUTILS_EXPORT void appendShort(QByteArray *ba, ushort s, Endianness = TargetByteOrder);
diff --git a/tools/runonphone/symbianutils/virtualserialdevice.cpp b/tools/runonphone/symbianutils/virtualserialdevice.cpp
new file mode 100644
index 0000000..4b733fd
--- /dev/null
+++ b/tools/runonphone/symbianutils/virtualserialdevice.cpp
@@ -0,0 +1,89 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the tools applications of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, 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.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "virtualserialdevice.h"
+#include <QtCore/QThread>
+#include <QtCore/QWaitCondition>
+
+namespace SymbianUtils {
+
+bool VirtualSerialDevice::isSequential() const
+{
+ return true;
+}
+
+VirtualSerialDevice::VirtualSerialDevice(const QString &aPortName, QObject *parent) :
+ QIODevice(parent), portName(aPortName), lock(QMutex::NonRecursive), emittingBytesWritten(false), waiterForBytesWritten(NULL)
+{
+ platInit();
+}
+
+const QString& VirtualSerialDevice::getPortName() const
+{
+ return portName;
+}
+
+void VirtualSerialDevice::close()
+{
+ if (isOpen()) {
+ QMutexLocker locker(&lock);
+ delete waiterForBytesWritten;
+ waiterForBytesWritten = NULL;
+ QIODevice::close();
+ platClose();
+ }
+}
+
+void VirtualSerialDevice::emitBytesWrittenIfNeeded(QMutexLocker& locker, qint64 len)
+{
+ if (waiterForBytesWritten) {
+ waiterForBytesWritten->wakeAll();
+ }
+ if (!emittingBytesWritten) {
+ emittingBytesWritten = true;
+ locker.unlock();
+ emit bytesWritten(len);
+ locker.relock();
+ emittingBytesWritten = false;
+ }
+}
+
+} // namespace SymbianUtils
diff --git a/tools/runonphone/symbianutils/virtualserialdevice.h b/tools/runonphone/symbianutils/virtualserialdevice.h
new file mode 100644
index 0000000..8da8849
--- /dev/null
+++ b/tools/runonphone/symbianutils/virtualserialdevice.h
@@ -0,0 +1,127 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the tools applications of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, 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.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef VIRTUALSERIALPORT_H
+#define VIRTUALSERIALPORT_H
+
+#include <QtCore/QIODevice>
+#include <QtCore/QString>
+#include <QtCore/QMutex>
+
+QT_BEGIN_NAMESPACE
+class QWaitCondition;
+QT_END_NAMESPACE
+
+#include "symbianutils_global.h"
+
+namespace SymbianUtils {
+
+class VirtualSerialDevicePrivate;
+
+class SYMBIANUTILS_EXPORT VirtualSerialDevice : public QIODevice
+{
+ Q_OBJECT
+public:
+ explicit VirtualSerialDevice(const QString &name, QObject *parent = 0);
+ ~VirtualSerialDevice();
+
+ bool open(OpenMode mode);
+ void close();
+ const QString &getPortName() const;
+ void flush();
+
+ qint64 bytesAvailable() const;
+ bool isSequential() const;
+ bool waitForBytesWritten(int msecs);
+ bool waitForReadyRead(int msecs);
+
+protected:
+ qint64 readData(char *data, qint64 maxSize);
+ qint64 writeData(const char *data, qint64 maxSize);
+
+private:
+ Q_DISABLE_COPY(VirtualSerialDevice)
+ void platInit();
+ void platClose();
+ void emitBytesWrittenIfNeeded(QMutexLocker &locker, qint64 len);
+
+private:
+ QString portName;
+ mutable QMutex lock;
+ QList<QByteArray> pendingWrites;
+ bool emittingBytesWritten;
+ QWaitCondition* waiterForBytesWritten;
+ VirtualSerialDevicePrivate *d;
+
+// Platform-specific stuff
+#ifdef Q_OS_WIN
+private:
+ qint64 writeNextBuffer(QMutexLocker &locker);
+ void doWriteCompleted(QMutexLocker &locker);
+private slots:
+ void writeCompleted();
+ void commEventOccurred();
+#endif
+
+#ifdef Q_OS_UNIX
+private:
+ bool tryWrite(const char *data, qint64 maxSize, qint64 &bytesWritten);
+ enum FlushPendingOption {
+ NothingSpecial = 0,
+ StopAfterWritingOneBuffer = 1,
+ EmitBytesWrittenAsync = 2, // Needed so we don't emit bytesWritten signal directly from writeBytes
+ };
+ Q_DECLARE_FLAGS(FlushPendingOptions, FlushPendingOption)
+ bool tryFlushPendingBuffers(QMutexLocker& locker, FlushPendingOptions flags = NothingSpecial);
+
+private slots:
+ void writeHasUnblocked(int fileHandle);
+
+signals:
+ void AsyncCall_emitBytesWrittenIfNeeded(qint64 len);
+
+#endif
+
+};
+
+} // namespace SymbianUtils
+
+#endif // VIRTUALSERIALPORT_H
diff --git a/tools/runonphone/symbianutils/virtualserialdevice_posix.cpp b/tools/runonphone/symbianutils/virtualserialdevice_posix.cpp
new file mode 100644
index 0000000..800f17d
--- /dev/null
+++ b/tools/runonphone/symbianutils/virtualserialdevice_posix.cpp
@@ -0,0 +1,344 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the tools applications of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, 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.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include <stdio.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <termios.h>
+#include <sys/ioctl.h>
+#include <unistd.h>
+#include <QtCore/QSocketNotifier>
+#include <QtCore/QTimer>
+#include <QtCore/QThread>
+#include <QtCore/QWaitCondition>
+#include "virtualserialdevice.h"
+
+namespace SymbianUtils {
+
+class VirtualSerialDevicePrivate
+{
+public:
+ int portHandle;
+ QSocketNotifier* readNotifier;
+ QSocketNotifier* writeUnblockedNotifier;
+};
+
+void VirtualSerialDevice::platInit()
+{
+ d = new VirtualSerialDevicePrivate;
+ d->portHandle = -1;
+ d->readNotifier = NULL;
+ d->writeUnblockedNotifier = NULL;
+ connect(this, SIGNAL(AsyncCall_emitBytesWrittenIfNeeded(qint64)), this, SIGNAL(bytesWritten(qint64)), Qt::QueuedConnection);
+}
+
+bool VirtualSerialDevice::open(OpenMode mode)
+{
+ if (isOpen()) return true;
+
+ d->portHandle = ::open(portName.toAscii().constData(), O_RDWR | O_NONBLOCK | O_NOCTTY);
+ if (d->portHandle == -1) {
+ setErrorString(tr("The port %1 could not be opened: %2 (POSIX error %3)").
+ arg(portName, QString::fromLocal8Bit(strerror(errno))).arg(errno));
+ return false;
+ }
+
+ struct termios termInfo;
+ if (tcgetattr(d->portHandle, &termInfo) < 0) {
+ setErrorString(tr("Unable to retrieve terminal settings of port %1: %2 (POSIX error %3)").
+ arg(portName, QString::fromLocal8Bit(strerror(errno))).arg(errno));
+ close();
+ return false;
+ }
+ cfmakeraw(&termInfo);
+ // Turn off terminal echo as not get messages back, among other things
+ termInfo.c_cflag |= CREAD|CLOCAL;
+ termInfo.c_cc[VTIME] = 0;
+ termInfo.c_lflag &= (~(ICANON|ECHO|ECHOE|ECHOK|ECHONL|ISIG));
+ termInfo.c_iflag &= (~(INPCK|IGNPAR|PARMRK|ISTRIP|ICRNL|IXANY|IXON|IXOFF));
+ termInfo.c_oflag &= (~OPOST);
+ termInfo.c_cc[VMIN] = 0;
+ termInfo.c_cc[VINTR] = _POSIX_VDISABLE;
+ termInfo.c_cc[VQUIT] = _POSIX_VDISABLE;
+ termInfo.c_cc[VSTART] = _POSIX_VDISABLE;
+ termInfo.c_cc[VSTOP] = _POSIX_VDISABLE;
+ termInfo.c_cc[VSUSP] = _POSIX_VDISABLE;
+
+ if (tcsetattr(d->portHandle, TCSAFLUSH, &termInfo) < 0) {
+ setErrorString(tr("Unable to apply terminal settings to port %1: %2 (POSIX error %3)").
+ arg(portName, QString::fromLocal8Bit(strerror(errno))).arg(errno));
+ close();
+ return false;
+ }
+
+ d->readNotifier = new QSocketNotifier(d->portHandle, QSocketNotifier::Read);
+ connect(d->readNotifier, SIGNAL(activated(int)), this, SIGNAL(readyRead()));
+
+ d->writeUnblockedNotifier = new QSocketNotifier(d->portHandle, QSocketNotifier::Write);
+ d->writeUnblockedNotifier->setEnabled(false);
+ connect(d->writeUnblockedNotifier, SIGNAL(activated(int)), this, SLOT(writeHasUnblocked(int)));
+
+ bool ok = QIODevice::open(mode | QIODevice::Unbuffered);
+ if (!ok) close();
+ return ok;
+}
+
+void VirtualSerialDevice::platClose()
+{
+ delete d->readNotifier;
+ d->readNotifier = NULL;
+
+ delete d->writeUnblockedNotifier;
+ d->writeUnblockedNotifier = NULL;
+
+ ::close(d->portHandle);
+ d->portHandle = -1;
+}
+
+VirtualSerialDevice::~VirtualSerialDevice()
+{
+ close();
+ delete d;
+}
+
+qint64 VirtualSerialDevice::bytesAvailable() const
+{
+ QMutexLocker locker(&lock);
+ if (!isOpen()) return 0;
+
+ int avail = 0;
+ if (ioctl(d->portHandle, FIONREAD, &avail) == -1) {
+ return 0;
+ }
+ return (qint64)avail + QIODevice::bytesAvailable();
+}
+
+qint64 VirtualSerialDevice::readData(char *data, qint64 maxSize)
+{
+ QMutexLocker locker(&lock);
+ int result = ::read(d->portHandle, data, maxSize);
+ if (result == -1 && errno == EAGAIN)
+ result = 0; // To Qt, 0 here means nothing ready right now, and -1 is reserved for permanent errors
+ return result;
+}
+
+qint64 VirtualSerialDevice::writeData(const char *data, qint64 maxSize)
+{
+ QMutexLocker locker(&lock);
+ qint64 bytesWritten;
+ bool needToWait = tryFlushPendingBuffers(locker, EmitBytesWrittenAsync);
+ if (!needToWait) {
+ needToWait = tryWrite(data, maxSize, bytesWritten);
+ if (needToWait && bytesWritten > 0) {
+ // Wrote some of the buffer, adjust pointers to point to the remainder that needs queueing
+ data += bytesWritten;
+ maxSize -= bytesWritten;
+ }
+ }
+
+ if (needToWait) {
+ pendingWrites.append(QByteArray(data, maxSize));
+ d->writeUnblockedNotifier->setEnabled(true);
+ // Now wait for the writeUnblocked signal or for a call to waitForBytesWritten
+ return bytesWritten + maxSize;
+ } else {
+ //emitBytesWrittenIfNeeded(locker, bytesWritten);
+ // Can't emit bytesWritten directly from writeData - means clients end up recursing
+ emit AsyncCall_emitBytesWrittenIfNeeded(bytesWritten);
+ return bytesWritten;
+ }
+}
+
+/* Returns true if EAGAIN encountered.
+ * if error occurred (other than EAGAIN) returns -1 in bytesWritten
+ * lock must be held. Doesn't emit signals or set notifiers.
+ */
+bool VirtualSerialDevice::tryWrite(const char *data, qint64 maxSize, qint64& bytesWritten)
+{
+ // Must be locked
+ bytesWritten = 0;
+ while (maxSize > 0) {
+ int result = ::write(d->portHandle, data, maxSize);
+ if (result == -1) {
+ if (errno == EAGAIN)
+ return true; // Need to wait
+ setErrorString(tr("Cannot write to port %1: %2 (POSIX error %3)").
+ arg(portName, QString::fromLocal8Bit(strerror(errno))).arg(errno));
+
+ bytesWritten = -1;
+ return false;
+ } else {
+ if (result == 0)
+ qWarning("%s: Zero bytes written to port %s!", Q_FUNC_INFO, qPrintable(portName));
+ bytesWritten += result;
+ maxSize -= result;
+ data += result;
+ }
+ }
+ return false; // If we reach here we've successfully written all the data without blocking
+}
+
+/* Returns true if EAGAIN encountered. Emits (or queues) bytesWritten for any buffers written.
+ * If stopAfterWritingOneBuffer is true, return immediately if a single buffer is written, rather than
+ * attempting to drain the whole queue.
+ * Doesn't modify notifier.
+ */
+bool VirtualSerialDevice::tryFlushPendingBuffers(QMutexLocker& locker, FlushPendingOptions flags)
+{
+ while (pendingWrites.count() > 0) {
+ // Try writing everything we've got, until we hit EAGAIN
+ const QByteArray& data = pendingWrites[0];
+ qint64 bytesWritten;
+ bool needToWait = tryWrite(data.constData(), data.size(), bytesWritten);
+ if (needToWait) {
+ if (bytesWritten > 0) {
+ // We wrote some of the data, update the pending queue
+ QByteArray remainder = data.mid(bytesWritten);
+ pendingWrites.removeFirst();
+ pendingWrites.insert(0, remainder);
+ }
+ return needToWait;
+ } else {
+ pendingWrites.removeFirst();
+ if (flags & EmitBytesWrittenAsync) {
+ emit AsyncCall_emitBytesWrittenIfNeeded(bytesWritten);
+ } else {
+ emitBytesWrittenIfNeeded(locker, bytesWritten);
+ }
+ if (flags & StopAfterWritingOneBuffer) return false;
+ // Otherwise go round loop again
+ }
+ }
+ return false; // no EAGAIN encountered
+}
+
+void VirtualSerialDevice::writeHasUnblocked(int fileHandle)
+{
+ Q_ASSERT(fileHandle == d->portHandle);
+ (void)fileHandle; // Compiler shutter-upper
+ d->writeUnblockedNotifier->setEnabled(false);
+
+ QMutexLocker locker(&lock);
+ bool needToWait = tryFlushPendingBuffers(locker);
+ if (needToWait) d->writeUnblockedNotifier->setEnabled(true);
+}
+
+// Copy of qt_safe_select from /qt/src/corelib/kernel/qeventdispatcher_unix.cpp
+// But without the timeout correction
+int safe_select(int nfds, fd_set *fdread, fd_set *fdwrite, fd_set *fdexcept,
+ const struct timeval *orig_timeout)
+{
+ if (!orig_timeout) {
+ // no timeout -> block forever
+ register int ret;
+ do {
+ ret = select(nfds, fdread, fdwrite, fdexcept, 0);
+ } while (ret == -1 && errno == EINTR);
+ return ret;
+ }
+
+ timeval timeout = *orig_timeout;
+
+ int ret;
+ forever {
+ ret = ::select(nfds, fdread, fdwrite, fdexcept, &timeout);
+ if (ret != -1 || errno != EINTR)
+ return ret;
+ }
+}
+
+bool VirtualSerialDevice::waitForBytesWritten(int msecs)
+{
+ QMutexLocker locker(&lock);
+ if (pendingWrites.count() == 0) return false;
+
+ if (QThread::currentThread() != thread()) {
+ // Wait for signal from main thread
+ unsigned long timeout = msecs;
+ if (msecs == -1) timeout = ULONG_MAX;
+ if (waiterForBytesWritten == NULL)
+ waiterForBytesWritten = new QWaitCondition;
+ return waiterForBytesWritten->wait(&lock, timeout);
+ }
+
+ d->writeUnblockedNotifier->setEnabled(false);
+ forever {
+ fd_set writeSet;
+ FD_ZERO(&writeSet);
+ FD_SET(d->portHandle, &writeSet);
+
+ struct timeval timeout;
+ if (msecs != -1) {
+ timeout.tv_sec = msecs / 1000;
+ timeout.tv_usec = (msecs % 1000) * 1000;
+ }
+ int ret = safe_select(d->portHandle+1, NULL, &writeSet, NULL, msecs == -1 ? NULL : &timeout);
+
+ if (ret == 0) {
+ // Timeout
+ return false;
+ } else if (ret < 0) {
+ setErrorString(tr("The function select() returned an error on port %1: %2 (POSIX error %3)").
+ arg(portName, QString::fromLocal8Bit(strerror(errno))).arg(errno));
+ return false;
+ } else {
+ bool needToWait = tryFlushPendingBuffers(locker, StopAfterWritingOneBuffer);
+ if (needToWait) {
+ // go round the select again
+ } else {
+ return true;
+ }
+ }
+ }
+}
+
+void VirtualSerialDevice::flush()
+{
+ while (waitForBytesWritten(-1)) { /* loop */ }
+ tcflush(d->portHandle, TCIOFLUSH);
+}
+
+bool VirtualSerialDevice::waitForReadyRead(int msecs)
+{
+ return QIODevice::waitForReadyRead(msecs); //TODO
+}
+
+} // namespace SymbianUtils
diff --git a/tools/runonphone/symbianutils/virtualserialdevice_win.cpp b/tools/runonphone/symbianutils/virtualserialdevice_win.cpp
new file mode 100644
index 0000000..389658c
--- /dev/null
+++ b/tools/runonphone/symbianutils/virtualserialdevice_win.cpp
@@ -0,0 +1,369 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the tools applications of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, 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.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "virtualserialdevice.h"
+#include <windows.h>
+#include <QtCore/private/qwineventnotifier_p.h>
+#include <QtCore/QThread>
+#include <QtCore/QWaitCondition>
+
+namespace SymbianUtils {
+
+class VirtualSerialDevicePrivate
+{
+public:
+ HANDLE portHandle;
+ OVERLAPPED writeOverlapped;
+ OVERLAPPED commEventOverlapped;
+ DWORD commEventMask;
+ QWinEventNotifier *writeCompleteNotifier;
+ QWinEventNotifier *commEventNotifier;
+};
+
+void VirtualSerialDevice::platInit()
+{
+ d = new VirtualSerialDevicePrivate;
+ d->portHandle = INVALID_HANDLE_VALUE;
+ d->writeCompleteNotifier = NULL;
+ memset(&d->writeOverlapped, 0, sizeof(OVERLAPPED));
+ d->commEventNotifier = NULL;
+ memset(&d->commEventOverlapped, 0, sizeof(OVERLAPPED));
+}
+
+QString windowsPortName(const QString& port)
+{
+ // Add the \\.\ to the name if it's a COM port and doesn't already have it
+ QString winPortName(port);
+ if (winPortName.startsWith(QLatin1String("COM"))) {
+ winPortName.prepend("\\\\.\\");
+ }
+ return winPortName;
+}
+
+// Copied from \creator\src\libs\utils\winutils.cpp
+QString winErrorMessage(unsigned long error)
+{
+ // Some of the windows error messages are a bit too obscure
+ switch (error)
+ {
+ case ERROR_FILE_NOT_FOUND:
+ case ERROR_NOT_FOUND:
+ return VirtualSerialDevice::tr("Port not found");
+ break;
+ case ERROR_ACCESS_DENIED:
+ return VirtualSerialDevice::tr("Port in use");
+ case ERROR_SEM_TIMEOUT: // Bluetooth ports sometimes return this
+ return VirtualSerialDevice::tr("Timed out");
+ case ERROR_NETWORK_UNREACHABLE:
+ return VirtualSerialDevice::tr("Port unreachable"); // I don't know what this error indicates... from observation, that the windows Bluetooth stack has got itself into a state and needs resetting
+ default:
+ break;
+ }
+
+ QString rc = QString::fromLatin1("#%1: ").arg(error);
+ ushort *lpMsgBuf;
+
+ const int len = FormatMessage(
+ FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
+ NULL, error, 0, (LPTSTR)&lpMsgBuf, 0, NULL);
+ if (len) {
+ rc = QString::fromUtf16(lpMsgBuf, len);
+ LocalFree(lpMsgBuf);
+ } else {
+ rc += QString::fromLatin1("<unknown error>");
+ }
+ return rc.trimmed();
+}
+
+bool VirtualSerialDevice::open(OpenMode mode)
+{
+ Q_ASSERT(QThread::currentThread() == thread());
+ if (isOpen()) return true;
+
+ d->portHandle = CreateFileA(windowsPortName(portName).toAscii(), GENERIC_READ|GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL);
+ if (d->portHandle == INVALID_HANDLE_VALUE) {
+ setErrorString(tr("The port %1 could not be opened: %2").
+ arg(portName, winErrorMessage(GetLastError())));
+ return false;
+ }
+
+ DCB commState;
+ memset(&commState, 0, sizeof(DCB));
+ commState.DCBlength = sizeof(DCB);
+ bool ok = GetCommState(d->portHandle, &commState);
+ if (ok) {
+ commState.BaudRate = CBR_115200;
+ commState.fBinary = TRUE;
+ commState.fParity = FALSE;
+ commState.fOutxCtsFlow = FALSE;
+ commState.fOutxDsrFlow = FALSE;
+ commState.fInX = FALSE;
+ commState.fOutX = FALSE;
+ commState.fNull = FALSE;
+ commState.fAbortOnError = FALSE;
+ commState.fDsrSensitivity = FALSE;
+ commState.fDtrControl = DTR_CONTROL_DISABLE;
+ commState.ByteSize = 8;
+ commState.Parity = NOPARITY;
+ commState.StopBits = ONESTOPBIT;
+ ok = SetCommState(d->portHandle, &commState);
+ }
+ if (!ok) {
+ qWarning("%s setting comm state", qPrintable(winErrorMessage(GetLastError())));
+ }
+
+ // http://msdn.microsoft.com/en-us/library/aa363190(v=vs.85).aspx says this means
+ // "the read operation is to return immediately with the bytes that have already been received, even if no bytes have been received"
+ COMMTIMEOUTS timeouts;
+ timeouts.ReadIntervalTimeout = MAXDWORD;
+ timeouts.ReadTotalTimeoutMultiplier = 0;
+ timeouts.ReadTotalTimeoutConstant = 0;
+ timeouts.WriteTotalTimeoutMultiplier = 0;
+ timeouts.WriteTotalTimeoutConstant = 0;
+ SetCommTimeouts(d->portHandle, &timeouts);
+
+ d->writeOverlapped.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
+ d->writeCompleteNotifier = new QWinEventNotifier(d->writeOverlapped.hEvent, this);
+ connect(d->writeCompleteNotifier, SIGNAL(activated(HANDLE)), this, SLOT(writeCompleted()));
+
+ // This is how we implement readyRead notifications
+ d->commEventOverlapped.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
+ d->commEventNotifier = new QWinEventNotifier(d->commEventOverlapped.hEvent, this);
+ connect(d->commEventNotifier, SIGNAL(activated(HANDLE)), this, SLOT(commEventOccurred()));
+
+ if (!SetCommMask(d->portHandle, EV_RXCHAR)) {
+ // What to do?
+ qWarning("%s: Could not set comm mask, err=%d", Q_FUNC_INFO, (int)GetLastError());
+ }
+ bool result = WaitCommEvent(d->portHandle, &d->commEventMask, &d->commEventOverlapped);
+ Q_ASSERT(result == false); // Can't see how it would make sense to be anything else...
+ (void)result; // For release build
+ if (GetLastError() != ERROR_IO_PENDING) {
+ setErrorString(tr("An error occurred while waiting for read notifications from %1: %2").
+ arg(portName, winErrorMessage(GetLastError())));
+ close();
+ return false;
+ }
+
+ ok = QIODevice::open(mode);
+ if (!ok) close();
+ return ok;
+}
+
+void VirtualSerialDevice::platClose()
+{
+ delete d->writeCompleteNotifier;
+ d->writeCompleteNotifier = NULL;
+ CloseHandle(d->writeOverlapped.hEvent);
+ d->writeOverlapped.hEvent = INVALID_HANDLE_VALUE;
+
+ delete d->commEventNotifier;
+ d->commEventNotifier = NULL;
+ d->commEventOverlapped.hEvent = INVALID_HANDLE_VALUE;
+
+ CloseHandle(d->portHandle);
+ d->portHandle = INVALID_HANDLE_VALUE;
+}
+
+VirtualSerialDevice::~VirtualSerialDevice()
+{
+ close();
+ delete d;
+}
+
+qint64 VirtualSerialDevice::bytesAvailable() const
+{
+ QMutexLocker locker(&lock);
+ if (!isOpen()) return 0;
+
+ qint64 avail = 0;
+ COMSTAT Status;
+ if (ClearCommError(d->portHandle, NULL, &Status)) {
+ avail = Status.cbInQue;
+ }
+ return avail + QIODevice::bytesAvailable();
+}
+
+void VirtualSerialDevice::commEventOccurred()
+{
+ DWORD event = d->commEventMask;
+ if (event & EV_RXCHAR) {
+ emit readyRead();
+ }
+ ResetEvent(d->commEventOverlapped.hEvent);
+ WaitCommEvent(d->portHandle, &d->commEventMask, &d->commEventOverlapped);
+}
+
+qint64 VirtualSerialDevice::readData(char *data, qint64 maxSize)
+{
+ QMutexLocker locker(&lock);
+ // We do our reads synchronously
+ OVERLAPPED readOverlapped;
+ memset(&readOverlapped, 0, sizeof(OVERLAPPED));
+ DWORD bytesRead;
+ BOOL done = ReadFile(d->portHandle, data, maxSize, &bytesRead, &readOverlapped);
+ if (done) return (qint64)bytesRead;
+
+ if (GetLastError() == ERROR_IO_PENDING) {
+ // Note the TRUE to wait for the read to complete
+ done = GetOverlappedResult(d->portHandle, &readOverlapped, &bytesRead, TRUE);
+ if (done) return (qint64)bytesRead;
+ }
+
+ // If we reach here an error has occurred
+ setErrorString(tr("An error occurred while reading from %1: %2").
+ arg(portName, winErrorMessage(GetLastError())));
+ return -1;
+}
+
+
+qint64 VirtualSerialDevice::writeData(const char *data, qint64 maxSize)
+{
+ QMutexLocker locker(&lock);
+
+ pendingWrites.append(QByteArray(data, maxSize)); // Can't see a way of doing async io safely without having to copy here...
+ if (pendingWrites.count() == 1) {
+ return writeNextBuffer(locker);
+ } else {
+ return maxSize;
+ }
+}
+
+qint64 VirtualSerialDevice::writeNextBuffer(QMutexLocker& locker)
+{
+ Q_UNUSED(locker)
+ // Must be locked on entry
+ qint64 bufLen = pendingWrites[0].length();
+ BOOL ok = WriteFile(d->portHandle, pendingWrites[0].constData(), bufLen, NULL, &d->writeOverlapped);
+ if (ok || GetLastError() == ERROR_IO_PENDING) {
+ // Apparently it can return true for a small asynchronous write...
+ // Hopefully it still gets signalled in the same way!
+
+ // Wait for signal via writeCompleted
+ return bufLen;
+ }
+ else {
+ setErrorString(tr("An error occurred while writing to %1: %2").
+ arg(portName, winErrorMessage(GetLastError())));
+ pendingWrites.removeFirst();
+ return -1;
+ }
+}
+
+void VirtualSerialDevice::writeCompleted()
+{
+ QMutexLocker locker(&lock);
+ if (pendingWrites.count() == 0) {
+ qWarning("%s: writeCompleted called when there are no pending writes on %s!",
+ Q_FUNC_INFO, qPrintable(portName));
+ return;
+ }
+
+ doWriteCompleted(locker);
+}
+
+void VirtualSerialDevice::doWriteCompleted(QMutexLocker &locker)
+{
+ // Must be locked on entry
+ ResetEvent(d->writeOverlapped.hEvent);
+
+ qint64 len = pendingWrites.first().length();
+ pendingWrites.removeFirst();
+
+ if (pendingWrites.count() > 0) {
+ // Get the next write started before notifying in case client calls waitForBytesWritten in their slot
+ writeNextBuffer(locker);
+ }
+
+ emitBytesWrittenIfNeeded(locker, len);
+}
+
+bool VirtualSerialDevice::waitForBytesWritten(int msecs)
+{
+ QMutexLocker locker(&lock);
+ if (pendingWrites.count() == 0) return false;
+
+ if (QThread::currentThread() != thread()) {
+ // Wait for signal from main thread
+ unsigned long timeout = msecs;
+ if (msecs == -1) timeout = ULONG_MAX;
+ if (waiterForBytesWritten == NULL)
+ waiterForBytesWritten = new QWaitCondition;
+ return waiterForBytesWritten->wait(&lock, timeout);
+ }
+
+ DWORD waitTime = msecs;
+ if (msecs == -1) waitTime = INFINITE; // Ok these are probably bitwise the same, but just to prove I've thought about it...
+ DWORD result = WaitForSingleObject(d->writeOverlapped.hEvent, waitTime); // Do I need WaitForSingleObjectEx and worry about alertable states?
+ if (result == WAIT_TIMEOUT) {
+ return false;
+ }
+ else if (result == WAIT_OBJECT_0) {
+ DWORD bytesWritten;
+ BOOL ok = GetOverlappedResult(d->portHandle, &d->writeOverlapped, &bytesWritten, TRUE);
+ if (!ok) {
+ setErrorString(tr("An error occurred while syncing on waitForBytesWritten for %1: %2").
+ arg(portName, winErrorMessage(GetLastError())));
+ return false;
+ }
+ Q_ASSERT(bytesWritten == (DWORD)pendingWrites.first().length());
+
+ doWriteCompleted(locker);
+ return true;
+ }
+ else {
+ setErrorString(QString("An error occured in waitForBytesWritten() for %1: %2").
+ arg(portName, winErrorMessage(GetLastError())));
+ return false;
+ }
+}
+
+void VirtualSerialDevice::flush()
+{
+ while (waitForBytesWritten(-1)) { /* loop */ }
+}
+
+bool VirtualSerialDevice::waitForReadyRead(int msecs)
+{
+ return QIODevice::waitForReadyRead(msecs); //TODO
+}
+
+} // namespace SymbianUtils
diff --git a/tools/runonphone/trksignalhandler.cpp b/tools/runonphone/trksignalhandler.cpp
index 062501a..59ff22c 100644
--- a/tools/runonphone/trksignalhandler.cpp
+++ b/tools/runonphone/trksignalhandler.cpp
@@ -77,10 +77,10 @@ private:
bool terminateNeeded;
};
-void TrkSignalHandler::copyingStarted()
+void TrkSignalHandler::copyingStarted(const QString &fileName)
{
if (d->loglevel > 0)
- d->out << "Copying..." << endl;
+ d->out << "Copying " << fileName << "..." << endl;
}
void TrkSignalHandler::canNotConnect(const QString &errorMessage)
@@ -103,10 +103,10 @@ void TrkSignalHandler::canNotCloseFile(const QString &filename, const QString &e
d->err << "Cannot close file (" << filename << ") - " << errorMessage << endl;
}
-void TrkSignalHandler::installingStarted()
+void TrkSignalHandler::installingStarted(const QString &packageName)
{
if (d->loglevel > 0)
- d->out << "Installing..." << endl;
+ d->out << "Installing" << packageName << "..." << endl;
}
void TrkSignalHandler::canNotInstall(const QString &packageFilename, const QString &errorMessage)
diff --git a/tools/runonphone/trksignalhandler.h b/tools/runonphone/trksignalhandler.h
index 5c76b19..e551c34 100644
--- a/tools/runonphone/trksignalhandler.h
+++ b/tools/runonphone/trksignalhandler.h
@@ -50,12 +50,12 @@ class TrkSignalHandler : public QObject
{
Q_OBJECT
public slots:
- void copyingStarted();
+ void copyingStarted(const QString &fileName);
void canNotConnect(const QString &errorMessage);
void canNotCreateFile(const QString &filename, const QString &errorMessage);
void canNotWriteFile(const QString &filename, const QString &errorMessage);
void canNotCloseFile(const QString &filename, const QString &errorMessage);
- void installingStarted();
+ void installingStarted(const QString &packageName);
void canNotInstall(const QString &packageFilename, const QString &errorMessage);
void installingFinished();
void startingApplication();