diff options
Diffstat (limited to 'src/runonphone')
35 files changed, 9538 insertions, 0 deletions
diff --git a/src/runonphone/main.cpp b/src/runonphone/main.cpp new file mode 100644 index 000000000..528154438 --- /dev/null +++ b/src/runonphone/main.cpp @@ -0,0 +1,277 @@ +/**************************************************************************** +** +** 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 <QCoreApplication> +#include <QTextStream> +#include <QStringList> +#include <QScopedPointer> +#include <QTimer> +#include <QFileInfo> +#include "symbianutils/trkutils.h" +#include "symbianutils/trkdevice.h" +#include "symbianutils/launcher.h" + +#include "trksignalhandler.h" +#include "serenum.h" +#include "ossignalconverter.h" + +void printUsage(QTextStream& outstream, QString exeName) +{ + outstream << exeName << " [options] [program] [program arguments]" << endl + << "-s, --sis <local file> specify sis file to install" << endl + << "-p, --portname <COMx> specify COM port to use by device name" << endl + << "-f, --portfriendlyname <substring> specify COM port to use by friendly name" << endl + << "-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> 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 + << endl + << "USB COM ports can usually be autodetected, use -p or -f to force a specific port." << 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; +} + +#define CHECK_PARAMETER_EXISTS if(!it.hasNext()) { printUsage(outstream, args[0]); return 1; } +int main(int argc, char *argv[]) +{ + QCoreApplication a(argc, argv); + + QString serialPortName; + QString serialPortFriendlyName; + QString sisFile; + QString exeFile; + QStringList cmdLine; + QStringList args = QCoreApplication::arguments(); + QTextStream outstream(stdout); + QTextStream errstream(stderr); + QString uploadLocalFile; + QString downloadRemoteFile; + QString downloadLocalFile; + int loglevel=1; + int timeout=0; + bool crashlog = true; + QString crashlogpath; + QListIterator<QString> it(args); + it.next(); //skip name of program + while (it.hasNext()) { + QString arg = it.next(); + + if (arg.startsWith("-")) { + if (arg == "--portname" || arg == "-p") { + CHECK_PARAMETER_EXISTS + serialPortName = it.next(); + } + else if (arg == "--portfriendlyname" || arg == "-f") { + CHECK_PARAMETER_EXISTS + serialPortFriendlyName = it.next(); + } + else if (arg == "--sis" || arg == "-s") { + CHECK_PARAMETER_EXISTS + sisFile = it.next(); + if (!QFileInfo(sisFile).exists()) { + errstream << "Sis file (" << sisFile << ") doesn't exist" << endl; + return 1; + } + } + else if (arg == "--upload" || arg == "-u") { + CHECK_PARAMETER_EXISTS + uploadLocalFile = it.next(); + if (!QFileInfo(uploadLocalFile).exists()) { + errstream << "Executable file (" << uploadLocalFile << ") doesn't exist" << endl; + return 1; + } + 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(); + } + else if (arg == "--timeout" || arg == "-t") { + CHECK_PARAMETER_EXISTS + bool ok; + timeout = it.next().toInt(&ok); + if (!ok) { + errstream << "Timeout must be specified in milliseconds" << endl; + return 1; + } + } + else if (arg == "--verbose" || arg == "-v") + loglevel=2; + else if (arg == "--quiet" || arg == "-q") + loglevel=0; + else if (arg == "--nocrashlog") + crashlog = false; + else if (arg == "--crashlogpath") { + CHECK_PARAMETER_EXISTS + crashlogpath = it.next(); + } + else + errstream << "unknown command line option " << arg << endl; + } else { + exeFile = arg; + while(it.hasNext()) { + cmdLine.append(it.next()); + } + } + } + + if (exeFile.isEmpty() && sisFile.isEmpty() && uploadLocalFile.isEmpty() && + (downloadLocalFile.isEmpty() || downloadRemoteFile.isEmpty())) { + printUsage(outstream, args[0]); + return 1; + } + + if (!uploadLocalFile.isEmpty() && (!downloadLocalFile.isEmpty() || !downloadRemoteFile.isEmpty())) { + errstream << "Upload option can't be used together with download" << endl; + printUsage(outstream, args[0]); + return 1; + } + + if (serialPortName.isEmpty()) { + if (loglevel > 0) + outstream << "Detecting serial ports" << endl; + foreach (const SerialPortId &id, enumerateSerialPorts(loglevel)) { + if (loglevel > 0) + outstream << "Port Name: " << id.portName << ", " + << "Friendly Name:" << id.friendlyName << endl; + if (!id.friendlyName.isEmpty() + && serialPortFriendlyName.isEmpty() + && (id.friendlyName.contains("symbian", Qt::CaseInsensitive) + || id.friendlyName.contains("s60", Qt::CaseInsensitive) + || id.friendlyName.contains("nokia", Qt::CaseInsensitive))) { + serialPortName = id.portName; + break; + } else if (!id.friendlyName.isEmpty() + && !serialPortFriendlyName.isEmpty() + && id.friendlyName.contains(serialPortFriendlyName)) { + serialPortName = id.portName; + break; + } + } + if (serialPortName.isEmpty()) { + errstream << "No phone found, ensure USB cable is connected or specify manually with -p" << endl; + return 1; + } + } + + QScopedPointer<trk::Launcher> launcher; + launcher.reset(new trk::Launcher(trk::Launcher::ActionPingOnly)); + QFileInfo exeInfo(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, QString("c:\\sys\\bin\\") + uploadInfo.fileName()); + } + 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); + + TrkSignalHandler handler; + handler.setLogLevel(loglevel); + handler.setCrashLogging(crashlog); + handler.setCrashLogPath(crashlogpath); + + 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())); + + QObject::connect(OsSignalConverter::instance(), SIGNAL(terminate()), launcher.data(), SLOT(terminate()), Qt::QueuedConnection); + + QTimer timer; + timer.setSingleShot(true); + QObject::connect(&timer, SIGNAL(timeout()), &handler, 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/src/runonphone/ossignalconverter.cpp b/src/runonphone/ossignalconverter.cpp new file mode 100644 index 000000000..71e705fee --- /dev/null +++ b/src/runonphone/ossignalconverter.cpp @@ -0,0 +1,121 @@ +/**************************************************************************** +** +** 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 "ossignalconverter_p.h" +#include <signal.h> +#include <QTimer> +#include <stdio.h> + +Q_GLOBAL_STATIC(OsSignalConverter, osSignalConverter); + +OsSignalConverter* OsSignalConverter::instance() +{ + return osSignalConverter(); +} + +OsSignalConverter::OsSignalConverter() +: d(new OsSignalConverterPrivate(this)) +{ +}; + +OsSignalConverter::~OsSignalConverter() +{ +} + +OsSignalConverterPrivate::OsSignalConverterPrivate(OsSignalConverter* owner) +: QObject(owner), q(owner), poller(new QTimer(this)) +{ + trap(); + connect(poller, SIGNAL(timeout()), this, SLOT(poll())); + poller->start(1000); +} + +OsSignalConverterPrivate::~OsSignalConverterPrivate() +{ + untrap(); +} + +void OsSignalConverterPrivate::trap() +{ + signal(SIGINT, handler); + signal(SIGTERM, handler); +#ifdef SIGBREAK + signal(SIGBREAK, handler); +#endif +#ifdef SIGHUP + signal(SIGHUP, handler); +#endif +#ifdef SIGQUIT + signal(SIGQUIT, handler); +#endif +} + +void OsSignalConverterPrivate::untrap() +{ + signal(SIGINT, SIG_DFL); + signal(SIGTERM, SIG_DFL); +#ifdef SIGBREAK + signal(SIGBREAK, SIG_DFL); +#endif +#ifdef SIGHUP + signal(SIGHUP, SIG_DFL); +#endif +#ifdef SIGQUIT + signal(SIGQUIT, SIG_DFL); +#endif +} + +void OsSignalConverterPrivate::handler(int sig) +{ + untrap(); //allow 2nd ctrl-c to really kill us + terminateRequest = sig; +} + +void OsSignalConverterPrivate::poll() +{ + if (terminateRequest) { + fprintf(stderr, "\n*** caught signal %d, terminating ***\n", terminateRequest); + poller->stop(); + emit q->terminate(); + } +} + +sig_atomic_t OsSignalConverterPrivate::terminateRequest; diff --git a/src/runonphone/ossignalconverter.h b/src/runonphone/ossignalconverter.h new file mode 100644 index 000000000..7c69a4b44 --- /dev/null +++ b/src/runonphone/ossignalconverter.h @@ -0,0 +1,63 @@ +/**************************************************************************** +** +** 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 OSSIGNALCONVERTER_H +#define OSSIGNALCONVERTER_H +#include <QObject> + +class OsSignalConverterPrivate; + +class OsSignalConverter : public QObject +{ + friend class OsSignalConverterPrivate; + Q_OBJECT +public: + static OsSignalConverter* instance(); + OsSignalConverter(); + ~OsSignalConverter(); +signals: + //emitted when this process is asked to quit, e.g. by SIGINT + void terminate(); +private: + OsSignalConverterPrivate *d; +}; + +#endif // OSSIGNALCONVERTER_H diff --git a/src/runonphone/ossignalconverter_p.h b/src/runonphone/ossignalconverter_p.h new file mode 100644 index 000000000..b7991c845 --- /dev/null +++ b/src/runonphone/ossignalconverter_p.h @@ -0,0 +1,71 @@ +/**************************************************************************** +** +** 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 OSSIGNALCONVERTER_P_H +#define OSSIGNALCONVERTER_P_H +#include "ossignalconverter.h" +#include <signal.h> + +class QTimer; +class OsSignalConverterPrivate : public QObject +{ + Q_OBJECT +public: + OsSignalConverterPrivate(OsSignalConverter* owner); + ~OsSignalConverterPrivate(); +private: + + static void trap(); + static void untrap(); + static void handler(int signal); + +private slots: + + void poll(); + +private: + + OsSignalConverter* q; + static sig_atomic_t terminateRequest; + QTimer *poller; +}; + +#endif // OSSIGNALCONVERTER_P_H diff --git a/src/runonphone/runonphone.pro b/src/runonphone/runonphone.pro new file mode 100644 index 000000000..7ff361ccf --- /dev/null +++ b/src/runonphone/runonphone.pro @@ -0,0 +1,35 @@ +TEMPLATE = app + +QT -= gui +CONFIG += console +CONFIG -= app_bundle + +include(symbianutils/symbianutils.pri) + +SOURCES += main.cpp \ + trksignalhandler.cpp \ + ossignalconverter.cpp + +HEADERS += trksignalhandler.h \ + serenum.h \ + ossignalconverter.h \ + ossignalconverter_p.h + +DEFINES += SYMBIANUTILS_INCLUDE_PRI + +windows { + SOURCES += serenum_win.cpp + LIBS += -lsetupapi \ + -luuid \ + -ladvapi32 +} +else:unix:!symbian { + SOURCES += serenum_unix.cpp + LIBS += -lusb +} +else { + SOURCES += serenum_stub.cpp +} + +target.path=$$[QT_INSTALL_BINS] +INSTALLS += target diff --git a/src/runonphone/serenum.h b/src/runonphone/serenum.h new file mode 100644 index 000000000..4ed7d4daa --- /dev/null +++ b/src/runonphone/serenum.h @@ -0,0 +1,56 @@ +/**************************************************************************** +** +** 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 WIN32SERENUM_H +#define WIN32SERENUM_H + +#include <QString> +#include <QList> + +struct SerialPortId +{ + QString portName; + QString friendlyName; +}; + +QList<SerialPortId> enumerateSerialPorts(int loglevel); + +#endif // WIN32SERENUM_H diff --git a/src/runonphone/serenum_stub.cpp b/src/runonphone/serenum_stub.cpp new file mode 100644 index 000000000..08b0fabd9 --- /dev/null +++ b/src/runonphone/serenum_stub.cpp @@ -0,0 +1,53 @@ +/**************************************************************************** +** +** 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 "serenum.h" +#include <QByteArray> +#include <QString> +#include <QDebug> + +QList<SerialPortId> enumerateSerialPorts() +{ + QList<SerialPortId> list; + qWarning() << "enumerateSerialPorts not implemented"; + return list; +} + diff --git a/src/runonphone/serenum_unix.cpp b/src/runonphone/serenum_unix.cpp new file mode 100644 index 000000000..8cc0b82a4 --- /dev/null +++ b/src/runonphone/serenum_unix.cpp @@ -0,0 +1,224 @@ +/**************************************************************************** +** +** 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 "serenum.h" +#include <QByteArray> +#include <QString> +#include <QDebug> +#include <QFileInfo> +#include <QDir> + +#include <usb.h> + +class InterfaceInfo +{ +public: + InterfaceInfo(const QString &mf, const QString &pr, int mfid, int prid); + QString manufacturer; + QString product; + int manufacturerid; + int productid; +}; + +InterfaceInfo::InterfaceInfo(const QString &mf, const QString &pr, int mfid, int prid) : + manufacturer(mf), + product(pr), + manufacturerid(mfid), + productid(prid) +{ + if(mf.isEmpty()) + manufacturer = QString("[%1]").arg(mfid, 4, 16, QChar('0')); + if(pr.isEmpty()) + product = QString("[%1]").arg(prid, 4, 16, QChar('0')); +} + +QList<SerialPortId> enumerateSerialPorts(int loglevel) +{ + QList<QString> eligibleInterfaces; + QList<InterfaceInfo> eligibleInterfacesInfo; + QList<SerialPortId> list; + + usb_init(); + usb_find_busses(); + usb_find_devices(); + + for (struct usb_bus *bus = usb_get_busses(); bus; bus = bus->next) { + for (struct usb_device *device = bus->devices; device; device = device->next) { + for (int n = 0; n < device->descriptor.bNumConfigurations && device->config; ++n) { + struct usb_config_descriptor &usbConfig =device->config[n]; + QList<int> usableInterfaces; + for (int m = 0; m < usbConfig.bNumInterfaces; ++m) { + for (int o = 0; o < usbConfig.interface[m].num_altsetting; ++o) { + struct usb_interface_descriptor &descriptor = usbConfig.interface[m].altsetting[o]; + if (descriptor.bInterfaceClass != 2 // "Communication" + || descriptor.bInterfaceSubClass != 2 // Abstract (modem) + || descriptor.bInterfaceProtocol != 255) // Vendor Specific + continue; + + unsigned char *buf = descriptor.extra; + unsigned int size = descriptor.extralen; + while (size >= 2 * sizeof(u_int8_t)) { + // for Communication devices there is a slave interface for the actual + // data transmission. + // the extra info stores that as a index for the interface + if (buf[0] >= 5 && buf[1] == 36 && buf[2] == 6) { // CDC Union + for (int i = 3; i < buf[0]; i++) + usableInterfaces.append((int) buf[i]); + } + size -= buf[0]; + buf += buf[0]; + } + } + } + + if (usableInterfaces.isEmpty()) + continue; + + QString manufacturerString; + QString productString; + + usb_dev_handle *devh = usb_open(device); + if (devh) { + QByteArray buf; + buf.resize(256); + int err = usb_get_string_simple(devh, device->descriptor.iManufacturer, buf.data(), buf.size()); + if (err < 0) { + if (loglevel > 1) + qDebug() << " can't read manufacturer name, error:" << err; + } else { + manufacturerString = QString::fromAscii(buf); + if (loglevel > 1) + qDebug() << " manufacturer:" << manufacturerString; + } + + buf.resize(256); + err = usb_get_string_simple(devh, device->descriptor.iProduct, buf.data(), buf.size()); + if (err < 0) { + if (loglevel > 1) + qDebug() << " can't read product name, error:" << err; + } else { + productString = QString::fromAscii(buf); + if (loglevel > 1) + qDebug() << " product:" << productString; + } + usb_close(devh); + } else if (loglevel > 0) { + qDebug() << " can't open usb device"; + } + + // second loop to find the actual data interface. + foreach (int i, usableInterfaces) { +#ifdef Q_OS_MAC + eligibleInterfaces << QString("^cu\\.usbmodem.*%1$") + .arg(QString("%1").arg(descriptor.bInterfaceNumber, 1, 16).toUpper()); +#else + // ### manufacturer and product strings are only readable as root :( + if (!manufacturerString.isEmpty() && !productString.isEmpty()) { + eligibleInterfaces << QString("usb-%1_%2-if%3") + .arg(manufacturerString.replace(QChar(' '), QChar('_'))) + .arg(productString.replace(QChar(' '), QChar('_'))) + .arg(i, 2, 16, QChar('0')); + } else { + eligibleInterfaces << QString("if%1").arg(i, 2, 16, QChar('0')); // fix! + } +#endif + } + eligibleInterfacesInfo << InterfaceInfo(manufacturerString, productString, device->descriptor.idVendor, device->descriptor.idProduct); + } + } + } + + if (loglevel > 1) + qDebug() << " searching for interfaces:" << eligibleInterfaces; + +#ifdef Q_OS_MAC + QDir dir("/dev/"); + bool allowAny = false; +#else + QDir dir("/dev/serial/by-id/"); + bool allowAny = eligibleInterfaces.isEmpty(); +#endif + foreach (const QFileInfo &info, dir.entryInfoList(QDir::System)) { + if (!info.isDir()) { + bool usable = allowAny; + QString friendlyName = info.fileName(); + foreach (const QString &iface, eligibleInterfaces) { + if (info.fileName().contains(QRegExp(iface))) { + if (loglevel > 1) + qDebug() << " found device file:" << info.fileName() << endl; +#ifdef Q_OS_MAC + friendlyName = eligibleInterfacesInfo[eligibleInterfaces.indexOf(iface)].product; +#endif + usable = true; + break; + } + } + if (!usable) + continue; + + SerialPortId id; + id.friendlyName = friendlyName; + id.portName = info.canonicalFilePath(); + list << id; + } + } + + if (list.isEmpty() && !eligibleInterfacesInfo.isEmpty() && loglevel > 0) { + qDebug() << "Possible USB devices found, but without serial drivers:"; + foreach(const InterfaceInfo &iface, eligibleInterfacesInfo) { + qDebug() << " Manufacturer:" + << iface.manufacturer + << "Product:" + << iface.product +#ifdef Q_OS_LINUX + << endl + << " Load generic driver using:" + << QString("sudo modprobe usbserial vendor=0x%1 product=0x%2") + .arg(iface.manufacturerid, 4, 16, QChar('0')) + .arg(iface.productid, 4, 16, QChar('0')); +#else + ; +#endif + } + } + return list; +} + diff --git a/src/runonphone/serenum_win.cpp b/src/runonphone/serenum_win.cpp new file mode 100644 index 000000000..7cc9d5215 --- /dev/null +++ b/src/runonphone/serenum_win.cpp @@ -0,0 +1,105 @@ +/**************************************************************************** +** +** 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 "serenum.h" +#include <QByteArray> +#include <QString> +#include <QDebug> +#include <windows.h> +#include <windef.h> +#include <setupapi.h> +#include <devguid.h> +#include <winreg.h> +#include <shlwapi.h> + +//{4d36e978-e325-11ce-bfc1-08002be10318} +//DEFINE_GUID(GUID_DEVCLASS_PORTS, 0x4D36E978, 0xE325, 0x11CE, 0xBF, 0xC1, 0x08, 0x00, 0x2B, 0xE1, 0x03, 0x18 ); + +QList<SerialPortId> enumerateSerialPorts(int) +{ + DWORD index=0; + SP_DEVINFO_DATA info; + GUID guid = GUID_DEVCLASS_PORTS; + HDEVINFO infoset = SetupDiGetClassDevs(&guid, 0, 0, DIGCF_PRESENT); + QString valueName(16384, 0); + QList<SerialPortId> list; + + for (index=0;;index++) { + ZeroMemory(&info, sizeof(SP_DEVINFO_DATA)); + info.cbSize = sizeof(SP_DEVINFO_DATA); + if (!SetupDiEnumDeviceInfo(infoset, index, &info)) + break; + QString friendlyName; + QString portName; + DWORD size=0; + SetupDiGetDeviceRegistryProperty(infoset, &info, SPDRP_FRIENDLYNAME, 0, 0, 0, &size); + QByteArray ba(size, 0); + if(SetupDiGetDeviceRegistryProperty(infoset, &info, SPDRP_FRIENDLYNAME, 0, (BYTE*)(ba.data()), size, 0)) { + friendlyName = QString((const QChar*)(ba.constData()), ba.size() / 2 - 1); + } + HKEY key = SetupDiOpenDevRegKey(infoset, &info, DICS_FLAG_GLOBAL, 0, DIREG_DEV, KEY_READ); + if(key != INVALID_HANDLE_VALUE) { + //RegGetValue not supported on XP, SHRegGetValue not supported by mingw, so use the old method of enumerating all the values + for (DWORD dwi=0;;dwi++) { + DWORD vsize = valueName.size(); + if (ERROR_SUCCESS == RegEnumValue(key, dwi, (WCHAR*)(valueName.data()), &vsize, 0, 0, 0, &size)) { + if (valueName.startsWith("PortName")) { + QByteArray ba(size, 0); + vsize = valueName.size(); + if(ERROR_SUCCESS == RegEnumValue(key, dwi, (WCHAR*)(valueName.data()), &vsize, 0, 0, (BYTE*)(ba.data()), &size)) { + portName = QString((const QChar*)(ba.constData()), ba.size() / 2 - 1); + } + } + } else { + break; + } + } + RegCloseKey(key); + } + SerialPortId id; + id.portName = portName; + id.friendlyName = friendlyName; + list.append(id); + } + SetupDiDestroyDeviceInfoList(infoset); + return list; +} + diff --git a/src/runonphone/symbianutils/bluetoothlistener.cpp b/src/runonphone/symbianutils/bluetoothlistener.cpp new file mode 100644 index 000000000..be232db04 --- /dev/null +++ b/src/runonphone/symbianutils/bluetoothlistener.cpp @@ -0,0 +1,224 @@ +/**************************************************************************** +** +** 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 "bluetoothlistener.h" +#include "trkdevice.h" + +#include <QtCore/QDebug> + +#ifdef Q_OS_UNIX +# include <unistd.h> +# include <signal.h> +#else +# include <windows.h> +#endif + +// Process id helpers. +#ifdef Q_OS_WIN +inline DWORD processId(const QProcess &p) +{ + if (const Q_PID processInfoStruct = p.pid()) + return processInfoStruct->dwProcessId; + return 0; +} +#else +inline Q_PID processId(const QProcess &p) +{ + return p.pid(); +} +#endif + + +enum { debug = 0 }; + +namespace trk { + +struct BluetoothListenerPrivate { + BluetoothListenerPrivate(); + QString device; + QProcess process; +#ifdef Q_OS_WIN + DWORD pid; +#else + Q_PID pid; +#endif + bool printConsoleMessages; + BluetoothListener::Mode mode; +}; + +BluetoothListenerPrivate::BluetoothListenerPrivate() : + pid(0), + printConsoleMessages(false), + mode(BluetoothListener::Listen) +{ +} + +BluetoothListener::BluetoothListener(QObject *parent) : + QObject(parent), + d(new BluetoothListenerPrivate) +{ + d->process.setProcessChannelMode(QProcess::MergedChannels); + + connect(&d->process, SIGNAL(readyReadStandardError()), + this, SLOT(slotStdError())); + connect(&d->process, SIGNAL(readyReadStandardOutput()), + this, SLOT(slotStdOutput())); + connect(&d->process, SIGNAL(finished(int, QProcess::ExitStatus)), + this, SLOT(slotProcessFinished(int,QProcess::ExitStatus))); + connect(&d->process, SIGNAL(error(QProcess::ProcessError)), + this, SLOT(slotProcessError(QProcess::ProcessError))); +} + +BluetoothListener::~BluetoothListener() +{ + const int trc = terminateProcess(); + if (debug) + qDebug() << "~BluetoothListener: terminated" << trc; + delete d; +} + +BluetoothListener::Mode BluetoothListener::mode() const +{ + return d->mode; +} + +void BluetoothListener::setMode(Mode m) +{ + d->mode = m; +} + +bool BluetoothListener::printConsoleMessages() const +{ + return d->printConsoleMessages; +} + +void BluetoothListener::setPrintConsoleMessages(bool p) +{ + d->printConsoleMessages = p; +} + +int BluetoothListener::terminateProcess() +{ + enum { TimeOutMS = 200 }; + if (debug) + qDebug() << "terminateProcess" << d->process.pid() << d->process.state(); + if (d->process.state() == QProcess::NotRunning) + return -1; + emitMessage(tr("%1: Stopping listener %2...").arg(d->device).arg(processId(d->process))); + // When listening, the process should terminate by itself after closing the connection + if (mode() == Listen && d->process.waitForFinished(TimeOutMS)) + return 0; +#ifdef Q_OS_UNIX + kill(d->process.pid(), SIGHUP); // Listens for SIGHUP + if (d->process.waitForFinished(TimeOutMS)) + return 1; +#endif + d->process.terminate(); + if (d->process.waitForFinished(TimeOutMS)) + return 2; + d->process.kill(); + return 3; +} + +bool BluetoothListener::start(const QString &device, QString *errorMessage) +{ + if (d->process.state() != QProcess::NotRunning) { + *errorMessage = QLatin1String("Internal error: Still running."); + return false; + } + d->device = device; + const QString binary = QLatin1String("rfcomm"); + QStringList arguments; + arguments << QLatin1String("-r") + << (d->mode == Listen ? QLatin1String("listen") : QLatin1String("watch")) + << device << QString(QLatin1Char('1')); + if (debug) + qDebug() << binary << arguments; + emitMessage(tr("%1: Starting Bluetooth listener %2...").arg(device, binary)); + d->pid = 0; + d->process.start(binary, arguments); + if (!d->process.waitForStarted()) { + *errorMessage = tr("Unable to run '%1': %2").arg(binary, d->process.errorString()); + return false; + } + d->pid = processId(d->process); // Forgets it after crash/termination + emitMessage(tr("%1: Bluetooth listener running (%2).").arg(device).arg(processId(d->process))); + return true; +} + +void BluetoothListener::slotStdOutput() +{ + emitMessage(QString::fromLocal8Bit(d->process.readAllStandardOutput())); +} + +void BluetoothListener::emitMessage(const QString &m) +{ + if (d->printConsoleMessages || debug) + qDebug("%s\n", qPrintable(m)); + emit message(m); +} + +void BluetoothListener::slotStdError() +{ + emitMessage(QString::fromLocal8Bit(d->process.readAllStandardError())); +} + +void BluetoothListener::slotProcessFinished(int ex, QProcess::ExitStatus state) +{ + switch (state) { + case QProcess::NormalExit: + emitMessage(tr("%1: Process %2 terminated with exit code %3.") + .arg(d->device).arg(d->pid).arg(ex)); + break; + case QProcess::CrashExit: + emitMessage(tr("%1: Process %2 crashed.").arg(d->device).arg(d->pid)); + break; + } + emit terminated(); +} + +void BluetoothListener::slotProcessError(QProcess::ProcessError error) +{ + emitMessage(tr("%1: Process error %2: %3") + .arg(d->device).arg(error).arg(d->process.errorString())); +} + +} // namespace trk diff --git a/src/runonphone/symbianutils/bluetoothlistener.h b/src/runonphone/symbianutils/bluetoothlistener.h new file mode 100644 index 000000000..38757275e --- /dev/null +++ b/src/runonphone/symbianutils/bluetoothlistener.h @@ -0,0 +1,103 @@ +/**************************************************************************** +** +** 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 BLUETOOTHLISTENER_H +#define BLUETOOTHLISTENER_H + +#include "symbianutils_global.h" + +#include <QtCore/QObject> +#include <QtCore/QProcess> + +namespace trk { +struct BluetoothListenerPrivate; + +/* BluetoothListener: Starts a helper process watching connections on a + * Bluetooth device, Linux only: + * The rfcomm command is used. It process can be started in the background + * while connection attempts (TrkDevice::open()) are made in the foreground. */ + +class SYMBIANUTILS_EXPORT BluetoothListener : public QObject +{ + Q_OBJECT + Q_DISABLE_COPY(BluetoothListener) +public: + // The Mode property must be set before calling start(). + enum Mode { + Listen, /* Terminate after client closed (read: Trk app + * on the phone terminated or disconnected).*/ + Watch // Keep running, watch for next connection from client + }; + + explicit BluetoothListener(QObject *parent = 0); + virtual ~BluetoothListener(); + + Mode mode() const; + void setMode(Mode m); + + bool start(const QString &device, QString *errorMessage); + + // Print messages on the console. + bool printConsoleMessages() const; + void setPrintConsoleMessages(bool p); + +signals: + void terminated(); + void message(const QString &); + +public slots: + void emitMessage(const QString &m); // accessed by starter + +private slots: + void slotStdOutput(); + void slotStdError(); + void slotProcessFinished(int, QProcess::ExitStatus); + void slotProcessError(QProcess::ProcessError error); + +private: + int terminateProcess(); + + BluetoothListenerPrivate *d; +}; + +} // namespace trk + +#endif // BLUETOOTHLISTENER_H diff --git a/src/runonphone/symbianutils/bluetoothlistener_gui.cpp b/src/runonphone/symbianutils/bluetoothlistener_gui.cpp new file mode 100644 index 000000000..c3f36b23a --- /dev/null +++ b/src/runonphone/symbianutils/bluetoothlistener_gui.cpp @@ -0,0 +1,111 @@ +/**************************************************************************** +** +** 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 "bluetoothlistener_gui.h" +#include "bluetoothlistener.h" +#include "communicationstarter.h" + +#include <QtGui/QMessageBox> +#include <QtGui/QPushButton> +#include <QtCore/QCoreApplication> +#include <QtCore/QDebug> + +namespace trk { + +SYMBIANUTILS_EXPORT PromptStartCommunicationResult + promptStartCommunication(BaseCommunicationStarter &starter, + const QString &msgBoxTitle, + const QString &msgBoxText, + QWidget *msgBoxParent, + QString *errorMessage) +{ + errorMessage->clear(); + // Initial connection attempt. + switch (starter.start()) { + case BaseCommunicationStarter::Started: + break; + case BaseCommunicationStarter::ConnectionSucceeded: + return PromptStartCommunicationConnected; + case BaseCommunicationStarter::StartError: + *errorMessage = starter.errorString(); + return PromptStartCommunicationError; + } + // Run the starter with the event loop of a message box, have the box + // closed by the signals of the starter. + QMessageBox messageBox(QMessageBox::Information, msgBoxTitle, msgBoxText, QMessageBox::Cancel, msgBoxParent); + QObject::connect(&starter, SIGNAL(connected()), &messageBox, SLOT(close())); + QObject::connect(&starter, SIGNAL(timeout()), &messageBox, SLOT(close())); + messageBox.exec(); + // Only starter.state() is reliable here to obtain the state. + switch (starter.state()) { + case AbstractBluetoothStarter::Running: + *errorMessage = QCoreApplication::translate("trk::promptStartCommunication", "Connection on %1 canceled.").arg(starter.device()); + return PromptStartCommunicationCanceled; + case AbstractBluetoothStarter::TimedOut: + *errorMessage = starter.errorString(); + return PromptStartCommunicationError; + case AbstractBluetoothStarter::Connected: + break; + } + return PromptStartCommunicationConnected; +} + +SYMBIANUTILS_EXPORT PromptStartCommunicationResult + promptStartSerial(BaseCommunicationStarter &starter, + QWidget *msgBoxParent, + QString *errorMessage) +{ + const QString title = QCoreApplication::translate("trk::promptStartCommunication", "Waiting for App TRK"); + const QString message = QCoreApplication::translate("trk::promptStartCommunication", "Waiting for App TRK to start on %1...").arg(starter.device()); + return promptStartCommunication(starter, title, message, msgBoxParent, errorMessage); +} + +SYMBIANUTILS_EXPORT PromptStartCommunicationResult + promptStartBluetooth(BaseCommunicationStarter &starter, + QWidget *msgBoxParent, + QString *errorMessage) +{ + const QString title = QCoreApplication::translate("trk::promptStartCommunication", "Waiting for Bluetooth Connection"); + const QString message = QCoreApplication::translate("trk::promptStartCommunication", "Connecting to %1...").arg(starter.device()); + return promptStartCommunication(starter, title, message, msgBoxParent, errorMessage); +} + +} // namespace trk diff --git a/src/runonphone/symbianutils/bluetoothlistener_gui.h b/src/runonphone/symbianutils/bluetoothlistener_gui.h new file mode 100644 index 000000000..d0edc7a26 --- /dev/null +++ b/src/runonphone/symbianutils/bluetoothlistener_gui.h @@ -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$ +** +****************************************************************************/ + +#ifndef BLUETOOTHLISTENER_GUI_H +#define BLUETOOTHLISTENER_GUI_H + +#include "symbianutils_global.h" + +#include <QtCore/QtGlobal> + +QT_BEGIN_NAMESPACE +class QWidget; +QT_END_NAMESPACE + +namespace trk { +class BaseCommunicationStarter; + +/* promptStartCommunication(): Convenience functions that + * prompt the user to start a communication (launching or + * connecting TRK) using a modal message box in which they can cancel. + * Pass in the starter with device and parameters set up. */ + +enum PromptStartCommunicationResult { + PromptStartCommunicationConnected, + PromptStartCommunicationCanceled, + PromptStartCommunicationError +}; + +SYMBIANUTILS_EXPORT PromptStartCommunicationResult + promptStartCommunication(BaseCommunicationStarter &starter, + const QString &msgBoxTitle, + const QString &msgBoxText, + QWidget *msgBoxParent, + QString *errorMessage); + +// Convenience to start a serial connection (messages prompting +// to launch Trk). +SYMBIANUTILS_EXPORT PromptStartCommunicationResult + promptStartSerial(BaseCommunicationStarter &starter, + QWidget *msgBoxParent, + QString *errorMessage); + +// Convenience to start blue tooth connection (messages +// prompting to connect). +SYMBIANUTILS_EXPORT PromptStartCommunicationResult + promptStartBluetooth(BaseCommunicationStarter &starter, + QWidget *msgBoxParent, + QString *errorMessage); +} // namespace trk + +#endif // BLUETOOTHLISTENER_GUI_H diff --git a/src/runonphone/symbianutils/callback.h b/src/runonphone/symbianutils/callback.h new file mode 100644 index 000000000..4ee77ea31 --- /dev/null +++ b/src/runonphone/symbianutils/callback.h @@ -0,0 +1,160 @@ +/**************************************************************************** +** +** 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 DEBUGGER_CALLBACK_H +#define DEBUGGER_CALLBACK_H + +#include "symbianutils_global.h" + +namespace trk { +namespace Internal { + +/* Helper class for the 1-argument functor: + * Cloneable base class for the implementation which is + * invokeable with the argument. */ +template <class Argument> +class CallbackImplBase +{ + Q_DISABLE_COPY(CallbackImplBase) +public: + CallbackImplBase() {} + virtual CallbackImplBase *clone() const = 0; + virtual void invoke(Argument a) = 0; + virtual ~CallbackImplBase() {} +}; + +/* Helper class for the 1-argument functor: Implementation for + * a class instance with a member function pointer. */ +template <class Class, class Argument> +class CallbackMemberPtrImpl : public CallbackImplBase<Argument> +{ +public: + typedef void (Class::*MemberFuncPtr)(Argument); + + CallbackMemberPtrImpl(Class *instance, + MemberFuncPtr memberFunc) : + m_instance(instance), + m_memberFunc(memberFunc) {} + + virtual CallbackImplBase<Argument> *clone() const + { + return new CallbackMemberPtrImpl<Class, Argument>(m_instance, m_memberFunc); + } + + virtual void invoke(Argument a) + { (m_instance->*m_memberFunc)(a); } +private: + Class *m_instance; + MemberFuncPtr m_memberFunc; +}; + +} // namespace Internal + +/* Default-constructible, copyable 1-argument functor providing an + * operator()(Argument) that invokes a member function of a class: + * \code +class Foo { +public: + void print(const std::string &); +}; +... +Foo foo; +Callback<const std::string &> f1(&foo, &Foo::print); +f1("test"); +\endcode */ + +template <class Argument> +class Callback +{ +public: + Callback() : m_impl(0) {} + + template <class Class> + Callback(Class *instance, void (Class::*memberFunc)(Argument)) : + m_impl(new Internal::CallbackMemberPtrImpl<Class,Argument>(instance, memberFunc)) + {} + + ~Callback() + { + clean(); + } + + Callback(const Callback &rhs) : + m_impl(0) + { + if (rhs.m_impl) + m_impl = rhs.m_impl->clone(); + } + + Callback &operator=(const Callback &rhs) + { + if (this != &rhs) { + clean(); + if (rhs.m_impl) + m_impl = rhs.m_impl->clone(); + } + return *this; + } + + bool isNull() const { return m_impl == 0; } + operator bool() const { return !isNull(); } + + void operator()(Argument a) + { + if (m_impl) + m_impl->invoke(a); + } + +private: + void clean() + { + if (m_impl) { + delete m_impl; + m_impl = 0; + } + } + + Internal::CallbackImplBase<Argument> *m_impl; +}; + +} // namespace trk + +#endif // DEBUGGER_CALLBACK_H diff --git a/src/runonphone/symbianutils/communicationstarter.cpp b/src/runonphone/symbianutils/communicationstarter.cpp new file mode 100644 index 000000000..602786b61 --- /dev/null +++ b/src/runonphone/symbianutils/communicationstarter.cpp @@ -0,0 +1,251 @@ +/**************************************************************************** +** +** 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 "communicationstarter.h" +#include "bluetoothlistener.h" +#include "trkdevice.h" + +#include <QtCore/QTimer> +#include <QtCore/QEventLoop> + +namespace trk { + +// --------------- AbstractBluetoothStarter +struct BaseCommunicationStarterPrivate { + explicit BaseCommunicationStarterPrivate(const BaseCommunicationStarter::TrkDevicePtr &d); + + const BaseCommunicationStarter::TrkDevicePtr trkDevice; + BluetoothListener *listener; + QTimer *timer; + int intervalMS; + int attempts; + int n; + QString errorString; + BaseCommunicationStarter::State state; +}; + +BaseCommunicationStarterPrivate::BaseCommunicationStarterPrivate(const BaseCommunicationStarter::TrkDevicePtr &d) : + trkDevice(d), + listener(0), + timer(0), + intervalMS(1000), + attempts(-1), + n(0), + state(BaseCommunicationStarter::TimedOut) +{ +} + +BaseCommunicationStarter::BaseCommunicationStarter(const TrkDevicePtr &trkDevice, QObject *parent) : + QObject(parent), + d(new BaseCommunicationStarterPrivate(trkDevice)) +{ +} + +BaseCommunicationStarter::~BaseCommunicationStarter() +{ + stopTimer(); + delete d; +} + +void BaseCommunicationStarter::stopTimer() +{ + if (d->timer && d->timer->isActive()) + d->timer->stop(); +} + +bool BaseCommunicationStarter::initializeStartupResources(QString *errorMessage) +{ + errorMessage->clear(); + return true; +} + +BaseCommunicationStarter::StartResult BaseCommunicationStarter::start() +{ + if (state() == Running) { + d->errorString = QLatin1String("Internal error, attempt to re-start BaseCommunicationStarter.\n"); + return StartError; + } + // Before we instantiate timers, and such, try to open the device, + // which should succeed if another listener is already running in + // 'Watch' mode + if (d->trkDevice->open(&(d->errorString))) + return ConnectionSucceeded; + // Pull up resources for next attempt + d->n = 0; + if (!initializeStartupResources(&(d->errorString))) + return StartError; + // Start timer + if (!d->timer) { + d->timer = new QTimer; + connect(d->timer, SIGNAL(timeout()), this, SLOT(slotTimer())); + } + d->timer->setInterval(d->intervalMS); + d->timer->setSingleShot(false); + d->timer->start(); + d->state = Running; + return Started; +} + +BaseCommunicationStarter::State BaseCommunicationStarter::state() const +{ + return d->state; +} + +int BaseCommunicationStarter::intervalMS() const +{ + return d->intervalMS; +} + +void BaseCommunicationStarter::setIntervalMS(int i) +{ + d->intervalMS = i; + if (d->timer) + d->timer->setInterval(i); +} + +int BaseCommunicationStarter::attempts() const +{ + return d->attempts; +} + +void BaseCommunicationStarter::setAttempts(int a) +{ + d->attempts = a; +} + +QString BaseCommunicationStarter::device() const +{ + return d->trkDevice->port(); +} + +QString BaseCommunicationStarter::errorString() const +{ + return d->errorString; +} + +void BaseCommunicationStarter::slotTimer() +{ + ++d->n; + // Check for timeout + if (d->attempts >= 0 && d->n >= d->attempts) { + stopTimer(); + d->errorString = tr("%1: timed out after %n attempts using an interval of %2ms.", 0, d->n) + .arg(d->trkDevice->port()).arg(d->intervalMS); + d->state = TimedOut; + emit timeout(); + } else { + // Attempt n to connect? + if (d->trkDevice->open(&(d->errorString))) { + stopTimer(); + const QString msg = tr("%1: Connection attempt %2 succeeded.").arg(d->trkDevice->port()).arg(d->n); + emit message(msg); + d->state = Connected; + emit connected(); + } else { + const QString msg = tr("%1: Connection attempt %2 failed: %3 (retrying)...") + .arg(d->trkDevice->port()).arg(d->n).arg(d->errorString); + emit message(msg); + } + } +} + +// --------------- AbstractBluetoothStarter + +AbstractBluetoothStarter::AbstractBluetoothStarter(const TrkDevicePtr &trkDevice, QObject *parent) : + BaseCommunicationStarter(trkDevice, parent) +{ +} + +bool AbstractBluetoothStarter::initializeStartupResources(QString *errorMessage) +{ + // Create the listener and forward messages to it. + BluetoothListener *listener = createListener(); + connect(this, SIGNAL(message(QString)), listener, SLOT(emitMessage(QString))); + return listener->start(device(), errorMessage); +} + +// -------- ConsoleBluetoothStarter +ConsoleBluetoothStarter::ConsoleBluetoothStarter(const TrkDevicePtr &trkDevice, + QObject *listenerParent, + QObject *parent) : +AbstractBluetoothStarter(trkDevice, parent), +m_listenerParent(listenerParent) +{ +} + +BluetoothListener *ConsoleBluetoothStarter::createListener() +{ + BluetoothListener *rc = new BluetoothListener(m_listenerParent); + rc->setMode(BluetoothListener::Listen); + rc->setPrintConsoleMessages(true); + return rc; +} + +bool ConsoleBluetoothStarter::startBluetooth(const TrkDevicePtr &trkDevice, + QObject *listenerParent, + int attempts, + QString *errorMessage) +{ + // Set up a console starter to print to stdout. + ConsoleBluetoothStarter starter(trkDevice, listenerParent); + starter.setAttempts(attempts); + switch (starter.start()) { + case Started: + break; + case ConnectionSucceeded: + return true; + case StartError: + *errorMessage = starter.errorString(); + return false; + } + // Run the starter with an event loop. @ToDo: Implement + // some asynchronous keypress read to cancel. + QEventLoop eventLoop; + connect(&starter, SIGNAL(connected()), &eventLoop, SLOT(quit())); + connect(&starter, SIGNAL(timeout()), &eventLoop, SLOT(quit())); + eventLoop.exec(QEventLoop::ExcludeUserInputEvents); + if (starter.state() != AbstractBluetoothStarter::Connected) { + *errorMessage = starter.errorString(); + return false; + } + return true; +} +} // namespace trk diff --git a/src/runonphone/symbianutils/communicationstarter.h b/src/runonphone/symbianutils/communicationstarter.h new file mode 100644 index 000000000..9fec5893c --- /dev/null +++ b/src/runonphone/symbianutils/communicationstarter.h @@ -0,0 +1,160 @@ +/**************************************************************************** +** +** 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 COMMUNICATIONSTARTER_H +#define COMMUNICATIONSTARTER_H + +#include "symbianutils_global.h" + +#include <QtCore/QSharedPointer> +#include <QtCore/QObject> + +namespace trk { +class TrkDevice; +class BluetoothListener; +struct BaseCommunicationStarterPrivate; + +/* BaseCommunicationStarter: A QObject that repeatedly tries to open a + * trk device until a connection succeeds or a timeout occurs (emitting + * signals), allowing to do something else in the foreground (local event loop + * [say QMessageBox] or some asynchronous operation). If the initial + * connection attempt in start() fails, the + * virtual initializeStartupResources() is called to initialize resources + * required to pull up the communication (namely Bluetooth listeners). + * The base class can be used as is to prompt the user to launch App TRK for a + * serial communication as this requires no further resource setup. */ + +class SYMBIANUTILS_EXPORT BaseCommunicationStarter : public QObject { + Q_OBJECT + Q_DISABLE_COPY(BaseCommunicationStarter) +public: + typedef QSharedPointer<TrkDevice> TrkDevicePtr; + + enum State { Running, Connected, TimedOut }; + + explicit BaseCommunicationStarter(const TrkDevicePtr& trkDevice, QObject *parent = 0); + virtual ~BaseCommunicationStarter(); + + int intervalMS() const; + void setIntervalMS(int i); + + int attempts() const; + void setAttempts(int a); + + QString device() const; + + State state() const; + QString errorString() const; + + enum StartResult { + Started, // Starter is now running. + ConnectionSucceeded, /* Initial connection attempt succeeded, + * no need to keep running. */ + StartError // Error occurred during start. + }; + + StartResult start(); + +signals: + void connected(); + void timeout(); + void message(const QString &); + +private slots: + void slotTimer(); + +protected: + virtual bool initializeStartupResources(QString *errorMessage); + +private: + inline void stopTimer(); + + BaseCommunicationStarterPrivate *d; +}; + +/* AbstractBluetoothStarter: Repeatedly tries to open a trk Bluetooth + * device. Note that in case a Listener is already running mode, the + * connection will succeed immediately. + * initializeStartupResources() is implemented to fire up the listener. + * Introduces a new virtual createListener() that derived classes must + * implement as a factory function that creates and sets up the + * listener (mode, message connection, etc). */ + +class SYMBIANUTILS_EXPORT AbstractBluetoothStarter : public BaseCommunicationStarter { + Q_OBJECT + Q_DISABLE_COPY(AbstractBluetoothStarter) +public: + +protected: + explicit AbstractBluetoothStarter(const TrkDevicePtr& trkDevice, QObject *parent = 0); + + // Implemented to fire up the listener. + virtual bool initializeStartupResources(QString *errorMessage); + // New virtual: Overwrite to create and parametrize the listener. + virtual BluetoothListener *createListener() = 0; +}; + +/* ConsoleBluetoothStarter: Convenience class for console processes. Creates a + * listener in "Listen" mode with the messages redirected to standard output. */ + +class SYMBIANUTILS_EXPORT ConsoleBluetoothStarter : public AbstractBluetoothStarter { + Q_OBJECT + Q_DISABLE_COPY(ConsoleBluetoothStarter) +public: + static bool startBluetooth(const TrkDevicePtr& trkDevice, + QObject *listenerParent, + int attempts, + QString *errorMessage); + +protected: + virtual BluetoothListener *createListener(); + +private: + explicit ConsoleBluetoothStarter(const TrkDevicePtr& trkDevice, + QObject *listenerParent, + QObject *parent = 0); + + QObject *m_listenerParent; +}; + +} // namespace trk + +#endif // COMMUNICATIONSTARTER_H diff --git a/src/runonphone/symbianutils/json.cpp b/src/runonphone/symbianutils/json.cpp new file mode 100644 index 000000000..755df5e1d --- /dev/null +++ b/src/runonphone/symbianutils/json.cpp @@ -0,0 +1,490 @@ +/**************************************************************************** +** +** 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 "json.h" + +#ifdef TODO_USE_CREATOR +#include <utils/qtcassert.h> +#endif // TODO_USE_CREATOR + +#include <QtCore/QByteArray> +#include <QtCore/QTextStream> +#include <QtCore/QDebug> +#include <QtCore/QStringList> + +#include <ctype.h> + +//#define DEBUG_JASON +#ifdef DEBUG_JASON +#define JDEBUG(s) qDebug() << s +#else +#define JDEBUG(s) +#endif + +namespace tcftrk { + +static void skipSpaces(const char *&from, const char *to) +{ + while (from != to && isspace(*from)) + ++from; +} + +QTextStream &operator<<(QTextStream &os, const JsonValue &mi) +{ + return os << mi.toString(); +} + +void JsonValue::parsePair(const char *&from, const char *to) +{ + skipSpaces(from, to); + JDEBUG("parsePair: " << QByteArray(from, to - from)); + m_name = parseCString(from, to); + skipSpaces(from, to); + while (from < to && *from != ':') { + JDEBUG("not a colon" << *from); + ++from; + } + ++from; + parseValue(from, to); + skipSpaces(from, to); +} + +QByteArray JsonValue::parseNumber(const char *&from, const char *to) +{ + QByteArray result; + if (from < to && *from == '-') // Leading '-'. + result.append(*from++); + while (from < to && *from >= '0' && *from <= '9') + result.append(*from++); + return result; +} + +QByteArray JsonValue::parseCString(const char *&from, const char *to) +{ + QByteArray result; + JDEBUG("parseCString: " << QByteArray(from, to - from)); + if (*from != '"') { + qDebug() << "JSON Parse Error, double quote expected"; + ++from; // So we don't hang + return QByteArray(); + } + const char *ptr = from; + ++ptr; + while (ptr < to) { + if (*ptr == '"') { + ++ptr; + result = QByteArray(from + 1, ptr - from - 2); + break; + } + if (*ptr == '\\') { + ++ptr; + if (ptr == to) { + qDebug() << "JSON Parse Error, unterminated backslash escape"; + from = ptr; // So we don't hang + return QByteArray(); + } + } + ++ptr; + } + from = ptr; + + int idx = result.indexOf('\\'); + if (idx >= 0) { + char *dst = result.data() + idx; + const char *src = dst + 1, *end = result.data() + result.length(); + do { + char c = *src++; + switch (c) { + case 'a': *dst++ = '\a'; break; + case 'b': *dst++ = '\b'; break; + case 'f': *dst++ = '\f'; break; + case 'n': *dst++ = '\n'; break; + case 'r': *dst++ = '\r'; break; + case 't': *dst++ = '\t'; break; + case 'v': *dst++ = '\v'; break; + case '"': *dst++ = '"'; break; + case '\\': *dst++ = '\\'; break; + default: + { + int chars = 0; + uchar prod = 0; + forever { + if (c < '0' || c > '7') { + --src; + break; + } + prod = prod * 8 + c - '0'; + if (++chars == 3 || src == end) + break; + c = *src++; + } + if (!chars) { + qDebug() << "JSON Parse Error, unrecognized backslash escape"; + return QByteArray(); + } + *dst++ = prod; + } + } + while (src != end) { + char c = *src++; + if (c == '\\') + break; + *dst++ = c; + } + } while (src != end); + *dst = 0; + result.truncate(dst - result.data()); + } + + JDEBUG("parseCString, got " << result); + return result; +} + + + +void JsonValue::parseValue(const char *&from, const char *to) +{ + JDEBUG("parseValue: " << QByteArray(from, to - from)); + switch (*from) { + case '{': + parseObject(from, to); + break; + case 't': + if (to - from >= 4 && qstrncmp(from, "true", 4) == 0) { + m_data = QByteArray(from, 4); + from += m_data.size(); + m_type = Boolean; + } + break; + case 'f': + if (to - from >= 5 && qstrncmp(from, "false", 5) == 0) { + m_data = QByteArray(from, 5); + from += m_data.size(); + m_type = Boolean; + } + break; + case 'n': + if (to - from >= 4 && qstrncmp(from, "null", 4) == 0) { + m_data = QByteArray(from, 4); + from += m_data.size(); + m_type = NullObject; + } + break; + case '[': + parseArray(from, to); + break; + case '"': + m_type = String; + m_data = parseCString(from, to); + break; + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + case '-': + m_type = Number; + m_data = parseNumber(from, to); + default: + break; + } +} + +void JsonValue::parseObject(const char *&from, const char *to) +{ + JDEBUG("parseObject: " << QByteArray(from, to - from)); +#ifdef TODO_USE_CREATOR + QTC_ASSERT(*from == '{', /**/); +#endif + ++from; + m_type = Object; + while (from < to) { + if (*from == '}') { + ++from; + break; + } + JsonValue child; + child.parsePair(from, to); + if (!child.isValid()) + return; + m_children += child; + if (*from == ',') + ++from; + } +} + +void JsonValue::parseArray(const char *&from, const char *to) +{ + JDEBUG("parseArray: " << QByteArray(from, to - from)); +#ifdef TODO_USE_CREATOR + QTC_ASSERT(*from == '[', /**/); +#endif + ++from; + m_type = Array; + while (from < to) { + if (*from == ']') { + ++from; + break; + } + JsonValue child; + child.parseValue(from, to); + if (child.isValid()) + m_children += child; + if (*from == ',') + ++from; + } +} + +void JsonValue::setStreamOutput(const QByteArray &name, const QByteArray &content) +{ + if (content.isEmpty()) + return; + JsonValue child; + child.m_type = String; + child.m_name = name; + child.m_data = content; + m_children += child; + if (m_type == Invalid) + m_type = Object; +} + +static QByteArray ind(int indent) +{ + return QByteArray(2 * indent, ' '); +} + +void JsonValue::dumpChildren(QByteArray * str, bool multiline, int indent) const +{ + for (int i = 0; i < m_children.size(); ++i) { + if (i != 0) { + *str += ','; + if (multiline) + *str += '\n'; + } + if (multiline) + *str += ind(indent); + *str += m_children.at(i).toString(multiline, indent); + } +} + +class MyString : public QString { +public: + ushort at(int i) const { return constData()[i].unicode(); } +}; + +template<class ST, typename CT> +inline ST escapeCStringTpl(const ST &ba) +{ + ST ret; + ret.reserve(ba.length() * 2); + for (int i = 0; i < ba.length(); ++i) { + CT c = ba.at(i); + switch (c) { + case '\\': ret += "\\\\"; break; + case '\a': ret += "\\a"; break; + case '\b': ret += "\\b"; break; + case '\f': ret += "\\f"; break; + case '\n': ret += "\\n"; break; + case '\r': ret += "\\r"; break; + case '\t': ret += "\\t"; break; + case '\v': ret += "\\v"; break; + case '"': ret += "\\\""; break; + default: + if (c < 32 || c == 127) { + ret += '\\'; + ret += '0' + (c >> 6); + ret += '0' + ((c >> 3) & 7); + ret += '0' + (c & 7); + } else { + ret += c; + } + } + } + return ret; +} + +QString JsonValue::escapeCString(const QString &ba) +{ + return escapeCStringTpl<MyString, ushort>(static_cast<const MyString &>(ba)); +} + +QByteArray JsonValue::escapeCString(const QByteArray &ba) +{ + return escapeCStringTpl<QByteArray, uchar>(ba); +} + +QByteArray JsonValue::toString(bool multiline, int indent) const +{ + QByteArray result; + switch (m_type) { + case Invalid: + if (multiline) + result += ind(indent) + "Invalid\n"; + else + result += "Invalid"; + break; + case String: + if (!m_name.isEmpty()) + result += m_name + "="; + result += '"' + escapeCString(m_data) + '"'; + break; + case Number: + if (!m_name.isEmpty()) + result += '"' + m_name + "\":"; + result += m_data; + break; + case Boolean: + case NullObject: + if (!m_name.isEmpty()) + result += '"' + m_name + "\":"; + result += m_data; + break; + case Object: + if (!m_name.isEmpty()) + result += m_name + '='; + if (multiline) { + result += "{\n"; + dumpChildren(&result, multiline, indent + 1); + result += '\n' + ind(indent) + "}"; + } else { + result += "{"; + dumpChildren(&result, multiline, indent + 1); + result += "}"; + } + break; + case Array: + if (!m_name.isEmpty()) + result += m_name + "="; + if (multiline) { + result += "[\n"; + dumpChildren(&result, multiline, indent + 1); + result += '\n' + ind(indent) + "]"; + } else { + result += "["; + dumpChildren(&result, multiline, indent + 1); + result += "]"; + } + break; + } + return result; +} + +void JsonValue::fromString(const QByteArray &ba) +{ + const char *from = ba.constBegin(); + const char *to = ba.constEnd(); + parseValue(from, to); +} + +JsonValue JsonValue::findChild(const char *name) const +{ + for (int i = 0; i < m_children.size(); ++i) + if (m_children.at(i).m_name == name) + return m_children.at(i); + return JsonValue(); +} + +void JsonInputStream::appendCString(const char *s) +{ + m_target.append('"'); + for (const char *p = s; *p; p++) { + if (*p == '"' || *p == '\\') + m_target.append('\\'); + m_target.append(*p); + } + m_target.append('"'); +} + +void JsonInputStream::appendString(const QString &in) +{ + if (in.isEmpty()) { + m_target.append("\"\""); + return; + } + + const QChar doubleQuote('"'); + const QChar backSlash('\\'); + QString rc; + const int inSize = in.size(); + rc.reserve(in.size() + 5); + rc.append(doubleQuote); + for (int i = 0; i < inSize; i++) { + const QChar c = in.at(i); + if (c == doubleQuote || c == backSlash) + rc.append(backSlash); + rc.append(c); + } + rc.append(doubleQuote); + m_target.append(rc.toUtf8()); + return; +} + +JsonInputStream &JsonInputStream::operator<<(const QStringList &in) +{ + m_target.append('['); + const int count = in.size(); + for (int i = 0 ; i < count; i++) { + if (i) + m_target.append(','); + appendString(in.at(i)); + } + m_target.append(']'); + return *this; +} + +JsonInputStream &JsonInputStream::operator<<(const QVector<QByteArray> &ba) +{ + m_target.append('['); + const int count = ba.size(); + for (int i = 0 ; i < count; i++) { + if (i) + m_target.append(','); + appendCString(ba.at(i).constData()); + } + m_target.append(']'); + return *this; +} + +JsonInputStream &JsonInputStream::operator<<(bool b) +{ + m_target.append(b ? "true" : "false"); + return *this; +} + +} // namespace tcftrk + diff --git a/src/runonphone/symbianutils/json.h b/src/runonphone/symbianutils/json.h new file mode 100644 index 000000000..a9190f812 --- /dev/null +++ b/src/runonphone/symbianutils/json.h @@ -0,0 +1,149 @@ +/**************************************************************************** +** +** 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 SYMBIANUTILS_JSON_H +#define SYMBIANUTILS_JSON_H + +#include "symbianutils_global.h" + +#include <QtCore/QByteArray> +#include <QtCore/QStringList> +#include <QtCore/QVector> + +namespace tcftrk { + +class SYMBIANUTILS_EXPORT JsonValue +{ +public: + JsonValue() : m_type(Invalid) {} + explicit JsonValue(const QByteArray &str) { fromString(str); } + + QByteArray m_name; + QByteArray m_data; + QList<JsonValue> m_children; + + enum Type { + Invalid, + String, + Number, + Boolean, + Object, + NullObject, + Array, + }; + + Type m_type; + + inline Type type() const { return m_type; } + inline QByteArray name() const { return m_name; } + inline bool hasName(const char *name) const { return m_name == name; } + + inline bool isValid() const { return m_type != Invalid; } + inline bool isNumber() const { return m_type == Number; } + inline bool isString() const { return m_type == String; } + inline bool isObject() const { return m_type == Object; } + inline bool isArray() const { return m_type == Array; } + + + inline QByteArray data() const { return m_data; } + inline const QList<JsonValue> &children() const { return m_children; } + inline int childCount() const { return m_children.size(); } + + const JsonValue &childAt(int index) const { return m_children[index]; } + JsonValue &childAt(int index) { return m_children[index]; } + JsonValue findChild(const char *name) const; + + QByteArray toString(bool multiline = false, int indent = 0) const; + void fromString(const QByteArray &str); + void setStreamOutput(const QByteArray &name, const QByteArray &content); + +private: + static QByteArray parseCString(const char *&from, const char *to); + static QByteArray parseNumber(const char *&from, const char *to); + static QByteArray escapeCString(const QByteArray &ba); + static QString escapeCString(const QString &ba); + void parsePair(const char *&from, const char *to); + void parseValue(const char *&from, const char *to); + void parseObject(const char *&from, const char *to); + void parseArray(const char *&from, const char *to); + + void dumpChildren(QByteArray *str, bool multiline, int indent) const; +}; + +/* Thin wrapper around QByteArray for formatting JSON input. Use as in: + * JsonInputStream(byteArray) << '{' << "bla" << ':' << "blup" << '}'; + * Note that strings get double quotes and JSON-escaping, characters should be + * used for the array/hash delimiters. + * */ +class SYMBIANUTILS_EXPORT JsonInputStream { +public: + explicit JsonInputStream(QByteArray &a) : m_target(a) {} + + JsonInputStream &operator<<(char c) { m_target.append(c); return *this; } + JsonInputStream &operator<<(const char *c) { appendCString(c); return *this; } + JsonInputStream &operator<<(const QByteArray &a) { appendCString(a.constData()); return *this; } + JsonInputStream &operator<<(const QString &c) { appendString(c); return *this; } + + // Format as array + JsonInputStream &operator<<(const QStringList &c); + + // Format as array + JsonInputStream &operator<<(const QVector<QByteArray> &ba); + + JsonInputStream &operator<<(bool b); + + JsonInputStream &operator<<(int i) + { m_target.append(QByteArray::number(i)); return *this; } + JsonInputStream &operator<<(unsigned i) + { m_target.append(QByteArray::number(i)); return *this; } + JsonInputStream &operator<<(quint64 i) + { m_target.append(QByteArray::number(i)); return *this; } + +private: + void appendString(const QString &); + void appendCString(const char *c); + + QByteArray &m_target; +}; + +} // namespace tcftrk + +#endif // SYMBIANUTILS_JSON_H diff --git a/src/runonphone/symbianutils/launcher.cpp b/src/runonphone/symbianutils/launcher.cpp new file mode 100644 index 000000000..a5d3d68ae --- /dev/null +++ b/src/runonphone/symbianutils/launcher.cpp @@ -0,0 +1,1036 @@ +/**************************************************************************** +** +** 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 "launcher.h" +#include "trkutils.h" +#include "trkutils_p.h" +#include "trkdevice.h" +#include "bluetoothlistener.h" +#include "symbiandevicemanager.h" + +#include <QtCore/QTimer> +#include <QtCore/QDateTime> +#include <QtCore/QVariant> +#include <QtCore/QDebug> +#include <QtCore/QQueue> +#include <QtCore/QFile> +#include <QtCore/QScopedPointer> + +#include <cstdio> + +namespace trk { + +struct CrashReportState { + CrashReportState(); + void clear(); + + typedef uint Thread; + typedef QList<Thread> Threads; + Threads threads; + + QList<uint> registers; + QByteArray stack; + uint sp; + uint fetchingStackPID; + uint fetchingStackTID; +}; + +CrashReportState::CrashReportState() +{ + clear(); +} + +void CrashReportState::clear() +{ + threads.clear(); + stack.clear(); + sp = fetchingStackPID = fetchingStackTID = 0; +} + +struct LauncherPrivate { + struct CopyState { + QString sourceFileName; + QString destinationFileName; + uint copyFileHandle; + QScopedPointer<QByteArray> data; + qint64 position; + QScopedPointer<QFile> localFile; + }; + + explicit LauncherPrivate(const TrkDevicePtr &d); + + TrkDevicePtr m_device; + QByteArray m_trkReadBuffer; + Launcher::State m_state; + + void logMessage(const QString &msg); + // Debuggee state + Session m_session; // global-ish data (process id, target information) + + CopyState m_copyState; + CopyState m_downloadState; + QString m_fileName; + QStringList m_commandLineArgs; + QString m_installFileName; + int m_verbose; + Launcher::Actions m_startupActions; + bool m_closeDevice; + CrashReportState m_crashReportState; +}; + +LauncherPrivate::LauncherPrivate(const TrkDevicePtr &d) : + m_device(d), + m_state(Launcher::Disconnected), + m_verbose(0), + m_closeDevice(true) +{ + if (m_device.isNull()) + m_device = TrkDevicePtr(new TrkDevice); +} + +Launcher::Launcher(Actions startupActions, + const TrkDevicePtr &dev, + QObject *parent) : + QObject(parent), + d(new LauncherPrivate(dev)) +{ + d->m_startupActions = startupActions; + connect(d->m_device.data(), SIGNAL(messageReceived(trk::TrkResult)), this, SLOT(handleResult(trk::TrkResult))); +} + +Launcher::~Launcher() +{ + // Destroyed before protocol was through: Close + if (d->m_closeDevice && d->m_device->isOpen()) + d->m_device->close(); + emit destroyed(d->m_device->port()); + logMessage("Shutting down.\n"); + delete d; +} + +Launcher::State Launcher::state() const +{ + return d->m_state; +} + +void Launcher::setState(State s) +{ + if (s != d->m_state) { + d->m_state = s; + emit stateChanged(s); + } +} + +void Launcher::addStartupActions(trk::Launcher::Actions startupActions) +{ + d->m_startupActions = Actions(d->m_startupActions | startupActions); +} + +void Launcher::setTrkServerName(const QString &name) +{ + d->m_device->setPort(name); +} + +QString Launcher::trkServerName() const +{ + return d->m_device->port(); +} + +TrkDevicePtr Launcher::trkDevice() const +{ + return d->m_device; +} + +void Launcher::setFileName(const QString &name) +{ + d->m_fileName = name; +} + +void Launcher::setCopyFileName(const QString &srcName, const QString &dstName) +{ + d->m_copyState.sourceFileName = srcName; + d->m_copyState.destinationFileName = dstName; +} + +void Launcher::setDownloadFileName(const QString &srcName, const QString &dstName) +{ + d->m_downloadState.sourceFileName = srcName; + d->m_downloadState.destinationFileName = dstName; +} + +void Launcher::setInstallFileName(const QString &name) +{ + d->m_installFileName = name; +} + +void Launcher::setCommandLineArgs(const QStringList &args) +{ + d->m_commandLineArgs = args; +} + +void Launcher::setSerialFrame(bool b) +{ + d->m_device->setSerialFrame(b); +} + +bool Launcher::serialFrame() const +{ + return d->m_device->serialFrame(); +} + + +bool Launcher::closeDevice() const +{ + return d->m_closeDevice; +} + +void Launcher::setCloseDevice(bool c) +{ + d->m_closeDevice = c; +} + +bool Launcher::startServer(QString *errorMessage) +{ + errorMessage->clear(); + d->m_crashReportState.clear(); + if (d->m_verbose) { + QString msg; + QTextStream str(&msg); + str.setIntegerBase(16); + str << "Actions=0x" << d->m_startupActions; + str.setIntegerBase(10); + str << " Port=" << trkServerName(); + 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; + 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; + logMessage(msg); + } + if (d->m_startupActions & ActionCopy) { + if (d->m_copyState.sourceFileName.isEmpty()) { + qWarning("No local filename given for copying package."); + return false; + } else if (d->m_copyState.destinationFileName.isEmpty()) { + qWarning("No remote filename given for copying package."); + return false; + } + } + if (d->m_startupActions & ActionInstall && d->m_installFileName.isEmpty()) { + qWarning("No package name given for installing."); + return false; + } + if (d->m_startupActions & ActionRun && d->m_fileName.isEmpty()) { + qWarning("No remote executable given for running."); + return false; + } + if (!d->m_device->isOpen() && !d->m_device->open(errorMessage)) + return false; + setState(Connecting); + // Set up the temporary 'waiting' state if we do not get immediate connection + QTimer::singleShot(1000, this, SLOT(slotWaitingForTrk())); + d->m_device->sendTrkInitialPing(); + d->m_device->sendTrkMessage(TrkDisconnect); // Disconnect, as trk might be still connected + d->m_device->sendTrkMessage(TrkSupported, TrkCallback(this, &Launcher::handleSupportMask)); + d->m_device->sendTrkMessage(TrkCpuType, TrkCallback(this, &Launcher::handleCpuType)); + d->m_device->sendTrkMessage(TrkVersions, TrkCallback(this, &Launcher::handleTrkVersion)); + if (d->m_startupActions != ActionPingOnly) + d->m_device->sendTrkMessage(TrkConnect, TrkCallback(this, &Launcher::handleConnect)); + return true; +} + +void Launcher::slotWaitingForTrk() +{ + // Set temporary state if we are still in connected state + if (state() == Connecting) + setState(WaitingForTrk); +} + +void Launcher::handleConnect(const TrkResult &result) +{ + if (result.errorCode()) { + emit canNotConnect(result.errorString()); + return; + } + setState(Connected); + if (d->m_startupActions & ActionCopy) + copyFileToRemote(); + else if (d->m_startupActions & ActionInstall) + installRemotePackageSilently(); + else if (d->m_startupActions & ActionRun) + startInferiorIfNeeded(); + else if (d->m_startupActions & ActionDownload) + copyFileFromRemote(); +} + +void Launcher::setVerbose(int v) +{ + d->m_verbose = v; + d->m_device->setVerbose(v); +} + +void Launcher::logMessage(const QString &msg) +{ + if (d->m_verbose) + qDebug() << "LAUNCHER: " << qPrintable(msg); +} + +void Launcher::handleFinished() +{ + if (d->m_closeDevice) + d->m_device->close(); + emit finished(); +} + +void Launcher::terminate() +{ + switch (state()) { + case DeviceDescriptionReceived: + case Connected: + if (d->m_session.pid) { + QByteArray ba; + appendShort(&ba, 0x0000, TargetByteOrder); + appendInt(&ba, d->m_session.pid, TargetByteOrder); + d->m_device->sendTrkMessage(TrkDeleteItem, TrkCallback(this, &Launcher::handleRemoteProcessKilled), ba); + return; + } + if (d->m_copyState.copyFileHandle) + closeRemoteFile(true); + disconnectTrk(); + break; + case Disconnected: + break; + case Connecting: + case WaitingForTrk: + setState(Disconnected); + handleFinished(); + break; + } +} + +void Launcher::handleRemoteProcessKilled(const TrkResult &result) +{ + Q_UNUSED(result) + disconnectTrk(); +} + +QString Launcher::msgStopped(uint pid, uint tid, uint address, const QString &why) +{ + return QString::fromLatin1("Process %1, thread %2 stopped at 0x%3: %4"). + arg(pid).arg(tid).arg(address, 0, 16). + arg(why.isEmpty() ? QString::fromLatin1("<Unknown reason>") : why); +} + +bool Launcher::parseNotifyStopped(const QByteArray &dataBA, + uint *pid, uint *tid, uint *address, + QString *why /* = 0 */) +{ + if (why) + why->clear(); + *address = *pid = *tid = 0; + if (dataBA.size() < 12) + return false; + const char *data = dataBA.data(); + *address = extractInt(data); + *pid = extractInt(data + 4); + *tid = extractInt(data + 8); + if (why && dataBA.size() >= 14) { + const unsigned short len = extractShort(data + 12); + if (len > 0) + *why = QString::fromLatin1(data + 14, len); + } + return true; +} + +void Launcher::handleResult(const TrkResult &result) +{ + QByteArray prefix = "READ BUF: "; + QByteArray str = result.toString().toUtf8(); + if (result.isDebugOutput) { // handle application output + QString msg; + if (result.multiplex == MuxTextTrace) { + if (result.data.length() > 8) { + quint64 timestamp = extractInt64(result.data) & 0x0FFFFFFFFFFFFFFFULL; + quint64 secs = timestamp / 1000000000; + quint64 ns = timestamp % 1000000000; + msg = QString("[%1.%2] %3").arg(secs).arg(ns).arg(QString(result.data.mid(8))); + logMessage("TEXT TRACE: " + msg); + } + } else { + logMessage("APPLICATION OUTPUT: " + stringFromArray(result.data)); + msg = result.data; + } + msg.replace("\r\n", "\n"); + if(!msg.endsWith('\n')) msg.append('\n'); + emit applicationOutputReceived(msg); + return; + } + switch (result.code) { + case TrkNotifyAck: + break; + case TrkNotifyNak: { // NAK + logMessage(prefix + "NAK: " + str); + //logMessage(prefix << "TOKEN: " << result.token); + logMessage(prefix + "ERROR: " + errorMessage(result.data.at(0))); + break; + } + case TrkNotifyStopped: { // Notified Stopped + QString reason; + uint pc; + uint pid; + uint tid; + parseNotifyStopped(result.data, &pid, &tid, &pc, &reason); + logMessage(prefix + msgStopped(pid, tid, pc, reason)); + emit(processStopped(pc, pid, tid, reason)); + d->m_device->sendTrkAck(result.token); + break; + } + case TrkNotifyException: { // Notify Exception (obsolete) + logMessage(prefix + "NOTE: EXCEPTION " + str); + d->m_device->sendTrkAck(result.token); + break; + } + case TrkNotifyInternalError: { // + logMessage(prefix + "NOTE: INTERNAL ERROR: " + str); + d->m_device->sendTrkAck(result.token); + break; + } + + // target->host OS notification + case TrkNotifyCreated: { // Notify Created + + if (result.data.size() < 10) + break; + const char *data = result.data.constData(); + const byte error = result.data.at(0); + Q_UNUSED(error) + const byte type = result.data.at(1); // type: 1 byte; for dll item, this value is 2. + const uint tid = extractInt(data + 6); //threadID: 4 bytes + Q_UNUSED(tid) + if (type == kDSOSDLLItem && result.data.size() >=20) { + const Library lib = Library(result); + d->m_session.libraries.push_back(lib); + emit libraryLoaded(lib); + } + QByteArray ba; + ba.append(result.data.mid(2, 8)); + d->m_device->sendTrkMessage(TrkContinue, TrkCallback(), ba, "CONTINUE"); + break; + } + case TrkNotifyDeleted: { // NotifyDeleted + const ushort itemType = (unsigned char)result.data.at(1); + const uint pid = result.data.size() >= 6 ? extractShort(result.data.constData() + 2) : 0; + const uint tid = result.data.size() >= 10 ? extractShort(result.data.constData() + 6) : 0; + Q_UNUSED(tid) + const ushort len = result.data.size() > 12 ? extractShort(result.data.constData() + 10) : ushort(0); + const QString name = len ? QString::fromAscii(result.data.mid(12, len)) : QString(); + logMessage(QString::fromLatin1("%1 %2 UNLOAD: %3"). + arg(QString::fromAscii(prefix)).arg(itemType ? QLatin1String("LIB") : QLatin1String("PROCESS")). + arg(name)); + d->m_device->sendTrkAck(result.token); + if (itemType == kDSOSProcessItem // process + && result.data.size() >= 10 + && d->m_session.pid == extractInt(result.data.data() + 6)) { + if (d->m_startupActions & ActionDownload) + copyFileFromRemote(); + else + disconnectTrk(); + } + else if (itemType == kDSOSDLLItem && len) { + // Remove libraries of process. + for (QList<Library>::iterator it = d->m_session.libraries.begin(); it != d->m_session.libraries.end(); ) { + if ((*it).pid == pid && (*it).name == name) { + emit libraryUnloaded(*it); + it = d->m_session.libraries.erase(it); + } else { + ++it; + } + } + } + break; + } + case TrkNotifyProcessorStarted: { // NotifyProcessorStarted + logMessage(prefix + "NOTE: PROCESSOR STARTED: " + str); + d->m_device->sendTrkAck(result.token); + break; + } + case TrkNotifyProcessorStandBy: { // NotifyProcessorStandby + logMessage(prefix + "NOTE: PROCESSOR STANDBY: " + str); + d->m_device->sendTrkAck(result.token); + break; + } + case TrkNotifyProcessorReset: { // NotifyProcessorReset + logMessage(prefix + "NOTE: PROCESSOR RESET: " + str); + d->m_device->sendTrkAck(result.token); + break; + } + default: { + logMessage(prefix + "INVALID: " + str); + break; + } + } +} + +QString Launcher::deviceDescription(unsigned verbose) const +{ + return d->m_session.deviceDescription(verbose); +} + +void Launcher::handleTrkVersion(const TrkResult &result) +{ + if (result.errorCode() || result.data.size() < 5) { + if (d->m_startupActions == ActionPingOnly) { + setState(Disconnected); + handleFinished(); + } + return; + } + d->m_session.trkAppVersion.trkMajor = result.data.at(1); + d->m_session.trkAppVersion.trkMinor = result.data.at(2); + d->m_session.trkAppVersion.protocolMajor = result.data.at(3); + d->m_session.trkAppVersion.protocolMinor = result.data.at(4); + setState(DeviceDescriptionReceived); + const QString msg = deviceDescription(); + emit deviceDescriptionReceived(trkServerName(), msg); + // Ping mode: Log & Terminate + if (d->m_startupActions == ActionPingOnly) { + qWarning("%s", qPrintable(msg)); + setState(Disconnected); + handleFinished(); + } +} + +static inline QString msgCannotOpenRemoteFile(const QString &fileName, const QString &message) +{ + return Launcher::tr("Cannot open remote file '%1': %2").arg(fileName, message); +} + +static inline QString msgCannotOpenLocalFile(const QString &fileName, const QString &message) +{ + return Launcher::tr("Cannot open '%1': %2").arg(fileName, message); +} + +void Launcher::handleFileCreation(const TrkResult &result) +{ + if (result.errorCode() || result.data.size() < 6) { + const QString msg = msgCannotOpenRemoteFile(d->m_copyState.destinationFileName, result.errorString()); + logMessage(msg); + emit canNotCreateFile(d->m_copyState.destinationFileName, msg); + disconnectTrk(); + return; + } + const char *data = result.data.data(); + d->m_copyState.copyFileHandle = extractInt(data + 2); + const QString localFileName = d->m_copyState.sourceFileName; + QFile file(localFileName); + d->m_copyState.position = 0; + if (!file.open(QIODevice::ReadOnly)) { + const QString msg = msgCannotOpenLocalFile(localFileName, file.errorString()); + logMessage(msg); + emit canNotOpenLocalFile(localFileName, msg); + closeRemoteFile(true); + disconnectTrk(); + return; + } + d->m_copyState.data.reset(new QByteArray(file.readAll())); + file.close(); + continueCopying(); +} + +void Launcher::handleFileOpened(const TrkResult &result) +{ + if (result.errorCode() || result.data.size() < 6) { + const QString msg = msgCannotOpenRemoteFile(d->m_downloadState.sourceFileName, result.errorString()); + logMessage(msg); + emit canNotOpenFile(d->m_downloadState.sourceFileName, msg); + disconnectTrk(); + return; + } + d->m_downloadState.position = 0; + const QString localFileName = d->m_downloadState.destinationFileName; + bool opened = false; + if (localFileName == QLatin1String("-")) { + d->m_downloadState.localFile.reset(new QFile); + opened = d->m_downloadState.localFile->open(stdout, QFile::WriteOnly); + } else { + d->m_downloadState.localFile.reset(new QFile(localFileName)); + opened = d->m_downloadState.localFile->open(QFile::WriteOnly | QFile::Truncate); + } + if (!opened) { + const QString msg = msgCannotOpenLocalFile(localFileName, d->m_downloadState.localFile->errorString()); + logMessage(msg); + emit canNotOpenLocalFile(localFileName, msg); + closeRemoteFile(true); + disconnectTrk(); + } + continueReading(); +} + +void Launcher::continueReading() +{ + QByteArray ba; + appendInt(&ba, d->m_downloadState.copyFileHandle, TargetByteOrder); + appendShort(&ba, 2048, TargetByteOrder); + d->m_device->sendTrkMessage(TrkReadFile, TrkCallback(this, &Launcher::handleRead), ba); +} + +void Launcher::handleRead(const TrkResult &result) +{ + if (result.errorCode() || result.data.size() < 4) { + d->m_downloadState.localFile->close(); + closeRemoteFile(true); + disconnectTrk(); + } else { + int length = extractShort(result.data.data() + 2); + //TRK doesn't tell us the file length, so we need to keep reading until it returns 0 length + if (length > 0) { + d->m_downloadState.localFile->write(result.data.data() + 4, length); + continueReading(); + } else { + closeRemoteFile(true); + disconnectTrk(); + } + } +} + +void Launcher::handleCopy(const TrkResult &result) +{ + if (result.errorCode() || result.data.size() < 4) { + closeRemoteFile(true); + emit canNotWriteFile(d->m_copyState.destinationFileName, result.errorString()); + disconnectTrk(); + } else { + continueCopying(extractShort(result.data.data() + 2)); + } +} + +void Launcher::continueCopying(uint lastCopiedBlockSize) +{ + qint64 size = d->m_copyState.data->length(); + d->m_copyState.position += lastCopiedBlockSize; + if (size == 0) + emit copyProgress(100); + else { + const qint64 hundred = 100; + const qint64 percent = qMin( (d->m_copyState.position * hundred) / size, hundred); + emit copyProgress(static_cast<int>(percent)); + } + if (d->m_copyState.position < size) { + QByteArray ba; + appendInt(&ba, d->m_copyState.copyFileHandle, TargetByteOrder); + appendString(&ba, d->m_copyState.data->mid(d->m_copyState.position, 2048), TargetByteOrder, false); + d->m_device->sendTrkMessage(TrkWriteFile, TrkCallback(this, &Launcher::handleCopy), ba); + } else { + closeRemoteFile(); + } +} + +void Launcher::closeRemoteFile(bool failed) +{ + QByteArray ba; + appendInt(&ba, d->m_copyState.copyFileHandle, TargetByteOrder); + appendDateTime(&ba, QDateTime::currentDateTime(), TargetByteOrder); + d->m_device->sendTrkMessage(TrkCloseFile, + failed ? TrkCallback() : TrkCallback(this, &Launcher::handleFileCopied), + ba); + d->m_copyState.data.reset(); + d->m_copyState.copyFileHandle = 0; + d->m_copyState.position = 0; +} + +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) + startInferiorIfNeeded(); + else if (d->m_startupActions & ActionDownload) + copyFileFromRemote(); + else + disconnectTrk(); +} + +void Launcher::handleCpuType(const TrkResult &result) +{ + logMessage("HANDLE CPU TYPE: " + result.toString()); + if(result.errorCode() || result.data.size() < 7) + return; + //---TRK------------------------------------------------------ + // Command: 0x80 Acknowledge + // Error: 0x00 + // [80 03 00 04 00 00 04 00 00 00] + d->m_session.cpuMajor = result.data.at(1); + d->m_session.cpuMinor = result.data.at(2); + d->m_session.bigEndian = result.data.at(3); + d->m_session.defaultTypeSize = result.data.at(4); + d->m_session.fpTypeSize = result.data.at(5); + d->m_session.extended1TypeSize = result.data.at(6); + //d->m_session.extended2TypeSize = result.data[6]; +} + +void Launcher::handleCreateProcess(const TrkResult &result) +{ + if (result.errorCode()) { + emit canNotRun(result.errorString()); + disconnectTrk(); + return; + } + // 40 00 00] + //logMessage(" RESULT: " + result.toString()); + // [80 08 00 00 00 01 B5 00 00 01 B6 78 67 40 00 00 40 00 00] + const char *data = result.data.data(); + d->m_session.pid = extractInt(data + 1); + d->m_session.tid = extractInt(data + 5); + d->m_session.codeseg = extractInt(data + 9); + d->m_session.dataseg = extractInt(data + 13); + if (d->m_verbose) { + const QString msg = QString::fromLatin1("Process id: %1 Thread id: %2 code: 0x%3 data: 0x%4"). + arg(d->m_session.pid).arg(d->m_session.tid).arg(d->m_session.codeseg, 0, 16). + arg(d->m_session.dataseg, 0 ,16); + logMessage(msg); + } + emit applicationRunning(d->m_session.pid); + //create a "library" entry for the executable which launched the process + Library lib; + lib.pid = d->m_session.pid; + lib.codeseg = d->m_session.codeseg; + lib.dataseg = d->m_session.dataseg; + lib.name = d->m_fileName.toUtf8(); + d->m_session.libraries << lib; + emit libraryLoaded(lib); + + QByteArray ba; + appendInt(&ba, d->m_session.pid); + appendInt(&ba, d->m_session.tid); + d->m_device->sendTrkMessage(TrkContinue, TrkCallback(), ba, "CONTINUE"); +} + +void Launcher::handleWaitForFinished(const TrkResult &result) +{ + logMessage(" FINISHED: " + stringFromArray(result.data)); + setState(Disconnected); + handleFinished(); +} + +void Launcher::handleSupportMask(const TrkResult &result) +{ + if (result.errorCode() || result.data.size() < 32) + return; + const char *data = result.data.data() + 1; + + if (d->m_verbose > 1) { + QString str = QLatin1String("SUPPORTED: "); + for (int i = 0; i < 32; ++i) { + for (int j = 0; j < 8; ++j) { + if (data[i] & (1 << j)) { + str.append(QString::number(i * 8 + j, 16)); + str.append(QLatin1Char(' ')); + } + } + } + logMessage(str); + } +} + +void Launcher::cleanUp() +{ + // + //---IDE------------------------------------------------------ + // Command: 0x41 Delete Item + // Sub Cmd: Delete Process + //ProcessID: 0x0000071F (1823) + // [41 24 00 00 00 00 07 1F] + QByteArray ba(2, char(0)); + appendInt(&ba, d->m_session.pid); + d->m_device->sendTrkMessage(TrkDeleteItem, TrkCallback(), ba, "Delete process"); + + //---TRK------------------------------------------------------ + // Command: 0x80 Acknowledge + // Error: 0x00 + // [80 24 00] + + //---IDE------------------------------------------------------ + // Command: 0x1C Clear Break + // [1C 25 00 00 00 0A 78 6A 43 40] + + //---TRK------------------------------------------------------ + // Command: 0xA1 Notify Deleted + // [A1 09 00 00 00 00 00 00 00 00 07 1F] + //---IDE------------------------------------------------------ + // Command: 0x80 Acknowledge + // Error: 0x00 + // [80 09 00] + + //---TRK------------------------------------------------------ + // Command: 0x80 Acknowledge + // Error: 0x00 + // [80 25 00] + + //---IDE------------------------------------------------------ + // Command: 0x1C Clear Break + // [1C 26 00 00 00 0B 78 6A 43 70] + //---TRK------------------------------------------------------ + // Command: 0x80 Acknowledge + // Error: 0x00 + // [80 26 00] + + + //---IDE------------------------------------------------------ + // Command: 0x02 Disconnect + // [02 27] +// sendTrkMessage(0x02, TrkCallback(this, &Launcher::handleDisconnect)); + //---TRK------------------------------------------------------ + // Command: 0x80 Acknowledge + // Error: 0x00 +} + +void Launcher::disconnectTrk() +{ + d->m_device->sendTrkMessage(TrkDisconnect, TrkCallback(this, &Launcher::handleWaitForFinished)); +} + +void Launcher::copyFileToRemote() +{ + emit copyingStarted(); + QByteArray ba; + ba.append(char(10)); //kDSFileOpenWrite | kDSFileOpenBinary + appendString(&ba, d->m_copyState.destinationFileName.toLocal8Bit(), TargetByteOrder, false); + d->m_device->sendTrkMessage(TrkOpenFile, TrkCallback(this, &Launcher::handleFileCreation), ba); +} + +void Launcher::copyFileFromRemote() +{ + emit copyingStarted(); + QByteArray ba; + ba.append(char(9)); //kDSFileOpenRead | kDSFileOpenBinary + appendString(&ba, d->m_downloadState.sourceFileName.toLocal8Bit(), TargetByteOrder, false); + d->m_device->sendTrkMessage(TrkOpenFile, TrkCallback(this, &Launcher::handleFileOpened), ba); +} + +void Launcher::installRemotePackageSilently() +{ + emit installingStarted(); + QByteArray ba; + ba.append('C'); + appendString(&ba, d->m_installFileName.toLocal8Bit(), TargetByteOrder, false); + d->m_device->sendTrkMessage(TrkInstallFile, TrkCallback(this, &Launcher::handleInstallPackageFinished), ba); +} + +void Launcher::handleInstallPackageFinished(const TrkResult &result) +{ + if (result.errorCode()) { + emit canNotInstall(d->m_installFileName, result.errorString()); + disconnectTrk(); + return; + } else { + emit installingFinished(); + } + if (d->m_startupActions & ActionRun) { + startInferiorIfNeeded(); + } else if (d->m_startupActions & ActionDownload) { + copyFileFromRemote(); + } else { + disconnectTrk(); + } +} + +QByteArray Launcher::startProcessMessage(const QString &executable, + const QStringList &arguments) +{ + // It's not started yet + QByteArray ba; + appendShort(&ba, 0, TargetByteOrder); // create new process (kDSOSProcessItem) + ba.append(char(0)); // options - currently unused + // One string consisting of binary terminated by '\0' and arguments terminated by '\0' + QByteArray commandLineBa = executable.toLocal8Bit(); + commandLineBa.append(char(0)); + if (!arguments.isEmpty()) + commandLineBa.append(arguments.join(QString(QLatin1Char(' '))).toLocal8Bit()); + appendString(&ba, commandLineBa, TargetByteOrder, true); + return ba; +} + +QByteArray Launcher::readMemoryMessage(uint pid, uint tid, uint from, uint len) +{ + QByteArray ba; + ba.reserve(11); + ba.append(char(0x8)); // Options, FIXME: why? + appendShort(&ba, len); + appendInt(&ba, from); + appendInt(&ba, pid); + appendInt(&ba, tid); + return ba; +} + +QByteArray Launcher::readRegistersMessage(uint pid, uint tid) +{ + QByteArray ba; + ba.reserve(15); + ba.append(char(0)); // Register set, only 0 supported + appendShort(&ba, 0); //R0 + appendShort(&ba, 16); // last register CPSR + appendInt(&ba, pid); + appendInt(&ba, tid); + return ba; +} + +void Launcher::startInferiorIfNeeded() +{ + emit startingApplication(); + if (d->m_session.pid != 0) { + logMessage("Process already 'started'"); + return; + } + d->m_device->sendTrkMessage(TrkCreateItem, TrkCallback(this, &Launcher::handleCreateProcess), + startProcessMessage(d->m_fileName, d->m_commandLineArgs)); // Create Item +} + +void Launcher::resumeProcess(uint pid, uint tid) +{ + QByteArray ba; + appendInt(&ba, pid, BigEndian); + appendInt(&ba, tid, BigEndian); + d->m_device->sendTrkMessage(TrkContinue, TrkCallback(), ba, "CONTINUE"); +} + +// Acquire a device from SymbianDeviceManager, return 0 if not available. +Launcher *Launcher::acquireFromDeviceManager(const QString &serverName, + QObject *parent, + QString *errorMessage) +{ + 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); + return 0; + } + // Wire release signal. + Launcher *rc = new Launcher(trk::Launcher::ActionPingOnly, device, parent); + connect(rc, SIGNAL(deviceDescriptionReceived(QString,QString)), + sdm, SLOT(setAdditionalInformation(QString,QString))); + connect(rc, SIGNAL(destroyed(QString)), sdm, SLOT(releaseDevice(QString))); + return rc; +} + +// Preliminary release of device, disconnecting the signal. +void Launcher::releaseToDeviceManager(Launcher *launcher) +{ + SymbianUtils::SymbianDeviceManager *sdm = SymbianUtils::SymbianDeviceManager::instance(); + // Disentangle launcher and its device, remove connection from destroyed + launcher->setCloseDevice(false); + TrkDevice *device = launcher->trkDevice().data(); + launcher->disconnect(device); + device->disconnect(launcher); + launcher->disconnect(sdm); + sdm->releaseDevice(launcher->trkServerName()); +} + +void Launcher::getRegistersAndCallStack(uint pid, uint tid) +{ + d->m_device->sendTrkMessage(TrkReadRegisters, + TrkCallback(this, &Launcher::handleReadRegisters), + Launcher::readRegistersMessage(pid, tid)); + d->m_crashReportState.fetchingStackPID = pid; + d->m_crashReportState.fetchingStackTID = tid; +} + +void Launcher::handleReadRegisters(const TrkResult &result) +{ + if(result.errorCode() || result.data.size() < (17*4)) { + terminate(); + return; + } + const char* data = result.data.constData() + 1; + d->m_crashReportState.registers.clear(); + d->m_crashReportState.stack.clear(); + for (int i=0;i<17;i++) { + uint r = extractInt(data); + data += 4; + d->m_crashReportState.registers.append(r); + } + d->m_crashReportState.sp = d->m_crashReportState.registers.at(13); + + const ushort len = 1024 - (d->m_crashReportState.sp % 1024); //read to 1k boundary first + const QByteArray ba = Launcher::readMemoryMessage(d->m_crashReportState.fetchingStackPID, + d->m_crashReportState.fetchingStackTID, + d->m_crashReportState.sp, + len); + d->m_device->sendTrkMessage(TrkReadMemory, TrkCallback(this, &Launcher::handleReadStack), ba); + d->m_crashReportState.sp += len; +} + +void Launcher::handleReadStack(const TrkResult &result) +{ + if (result.errorCode()) { + //error implies memory fault when reaching end of stack + emit registersAndCallStackReadComplete(d->m_crashReportState.registers, d->m_crashReportState.stack); + return; + } + + const uint len = extractShort(result.data.constData() + 1); + d->m_crashReportState.stack.append(result.data.mid(3, len)); + + if (d->m_crashReportState.sp - d->m_crashReportState.registers.at(13) > 0x10000) { + //read enough stack, stop here + emit registersAndCallStackReadComplete(d->m_crashReportState.registers, d->m_crashReportState.stack); + return; + } + //read 1k more + const QByteArray ba = Launcher::readMemoryMessage(d->m_crashReportState.fetchingStackPID, + d->m_crashReportState.fetchingStackTID, + d->m_crashReportState.sp, + 1024); + d->m_device->sendTrkMessage(TrkReadMemory, TrkCallback(this, &Launcher::handleReadStack), ba); + d->m_crashReportState.sp += 1024; +} + +} // namespace trk diff --git a/src/runonphone/symbianutils/launcher.h b/src/runonphone/symbianutils/launcher.h new file mode 100644 index 000000000..fac65b569 --- /dev/null +++ b/src/runonphone/symbianutils/launcher.h @@ -0,0 +1,212 @@ +/**************************************************************************** +** +** 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 LAUNCHER_H +#define LAUNCHER_H + +#include "trkdevice.h" +#include "trkutils.h" + +#include <QtCore/QObject> +#include <QtCore/QVariant> +#include <QtCore/QSharedPointer> + +namespace trk { + +struct TrkResult; +struct TrkMessage; +struct LauncherPrivate; + +typedef QSharedPointer<TrkDevice> TrkDevicePtr; + +class SYMBIANUTILS_EXPORT Launcher : public QObject +{ + Q_OBJECT + Q_DISABLE_COPY(Launcher) +public: + typedef void (Launcher::*TrkCallBack)(const TrkResult &); + + enum Actions { + ActionPingOnly = 0x0, + ActionCopy = 0x1, + ActionInstall = 0x2, + ActionCopyInstall = ActionCopy | ActionInstall, + ActionRun = 0x4, + ActionDownload = 0x8, + ActionCopyRun = ActionCopy | ActionRun, + ActionInstallRun = ActionInstall | ActionRun, + ActionCopyInstallRun = ActionCopy | ActionInstall | ActionRun + }; + + enum State { Disconnected, Connecting, Connected, + WaitingForTrk, // This occurs only if the initial ping times out after + // a reasonable timeout, indicating that Trk is not + // running. Note that this will never happen with + // Bluetooth as communication immediately starts + // after connecting. + DeviceDescriptionReceived }; + + explicit Launcher(trk::Launcher::Actions startupActions = trk::Launcher::ActionPingOnly, + const TrkDevicePtr &trkDevice = TrkDevicePtr(), + QObject *parent = 0); + ~Launcher(); + + State state() const; + + void addStartupActions(trk::Launcher::Actions startupActions); + void setTrkServerName(const QString &name); + QString trkServerName() const; + void setFileName(const QString &name); + void setCopyFileName(const QString &srcName, const QString &dstName); + void setDownloadFileName(const QString &srcName, const QString &dstName); + void setInstallFileName(const QString &name); + void setCommandLineArgs(const QStringList &args); + bool startServer(QString *errorMessage); + void setVerbose(int v); + void setSerialFrame(bool b); + bool serialFrame() const; + // Close device or leave it open + bool closeDevice() const; + void setCloseDevice(bool c); + + TrkDevicePtr trkDevice() const; + + // becomes valid after successful execution of ActionPingOnly + QString deviceDescription(unsigned verbose = 0u) const; + + // Acquire a device from SymbianDeviceManager, return 0 if not available. + // The device will be released on destruction. + static Launcher *acquireFromDeviceManager(const QString &serverName, + QObject *parent, + QString *errorMessage); + // Preliminary release of device, disconnecting the signal. + static void releaseToDeviceManager(Launcher *l); + + // Create Trk message to start a process. + static QByteArray startProcessMessage(const QString &executable, + const QStringList &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); + // Parse a TrkNotifyStopped message + static bool parseNotifyStopped(const QByteArray &a, + uint *pid, uint *tid, uint *address, + QString *why = 0); + // Helper message + static QString msgStopped(uint pid, uint tid, uint address, const QString &why); + +signals: + void deviceDescriptionReceived(const QString &port, const QString &description); + void copyingStarted(); + 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 canNotInstall(const QString &packageFilename, const QString &errorMessage); + void installingFinished(); + void startingApplication(); + void applicationRunning(uint pid); + void canNotRun(const QString &errorMessage); + void finished(); + void applicationOutputReceived(const QString &output); + void copyProgress(int percent); + void stateChanged(int); + 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); + void registersAndCallStackReadComplete(const QList<uint>& registers, const QByteArray& stack); + // Emitted by the destructor, for releasing devices of SymbianDeviceManager by name + void destroyed(const QString &serverName); + +public slots: + void terminate(); + void resumeProcess(uint pid, uint tid); + //can be used to obtain traceback after a breakpoint / exception + void getRegistersAndCallStack(uint pid, uint tid); + +private slots: + void handleResult(const trk::TrkResult &data); + void slotWaitingForTrk(); + +private: + // kill process and breakpoints + void cleanUp(); + void disconnectTrk(); + + void handleRemoteProcessKilled(const TrkResult &result); + void handleConnect(const TrkResult &result); + void handleFileCreation(const TrkResult &result); + void handleFileOpened(const TrkResult &result); + void handleCopy(const TrkResult &result); + void handleRead(const TrkResult &result); + void continueCopying(uint lastCopiedBlockSize = 0); + void continueReading(); + void closeRemoteFile(bool failed = false); + void handleFileCopied(const TrkResult &result); + void handleInstallPackageFinished(const TrkResult &result); + void handleCpuType(const TrkResult &result); + void handleCreateProcess(const TrkResult &result); + void handleWaitForFinished(const TrkResult &result); + void handleStop(const TrkResult &result); + void handleSupportMask(const TrkResult &result); + void handleTrkVersion(const TrkResult &result); + void handleReadRegisters(const TrkResult &result); + void handleReadStack(const TrkResult &result); + + void copyFileToRemote(); + void copyFileFromRemote(); + void installRemotePackageSilently(); + void startInferiorIfNeeded(); + void handleFinished(); + + void logMessage(const QString &msg); + void setState(State s); + + LauncherPrivate *d; +}; + +} // namespace Trk + +#endif // LAUNCHER_H diff --git a/src/runonphone/symbianutils/symbiandevicemanager.cpp b/src/runonphone/symbianutils/symbiandevicemanager.cpp new file mode 100644 index 000000000..8d65d182c --- /dev/null +++ b/src/runonphone/symbianutils/symbiandevicemanager.cpp @@ -0,0 +1,489 @@ +/**************************************************************************** +** +** 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 "symbiandevicemanager.h" +#include "trkdevice.h" + +#include <QtCore/QSettings> +#include <QtCore/QStringList> +#include <QtCore/QFileInfo> +#include <QtCore/QtDebug> +#include <QtCore/QTextStream> +#include <QtCore/QSharedData> +#include <QtCore/QScopedPointer> +#include <QtCore/QSignalMapper> + +namespace SymbianUtils { + +enum { debug = 0 }; + +static const char REGKEY_CURRENT_CONTROL_SET[] = "HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet"; +static const char USBSER[] = "Services/usbser/Enum"; + +const char *SymbianDeviceManager::linuxBlueToothDeviceRootC = "/dev/rfcomm"; + +// ------------- SymbianDevice +class SymbianDeviceData : public QSharedData { +public: + SymbianDeviceData(); + ~SymbianDeviceData(); + + inline bool isOpen() const { return !device.isNull() && device->isOpen(); } + void forcedClose(); + + QString portName; + QString friendlyName; + QString deviceDesc; + QString manufacturer; + QString additionalInformation; + + DeviceCommunicationType type; + QSharedPointer<trk::TrkDevice> device; + bool deviceAcquired; +}; + +SymbianDeviceData::SymbianDeviceData() : + type(SerialPortCommunication), + deviceAcquired(false) +{ +} + +SymbianDeviceData::~SymbianDeviceData() +{ + forcedClose(); +} + +void SymbianDeviceData::forcedClose() +{ + // Close the device when unplugging. Should devices be in 'acquired' state, + // their owners should hit on write failures. + // Apart from the <shared item> destructor, also called by the devicemanager + // to ensure it also happens if other shared instances are still around. + if (isOpen()) { + if (deviceAcquired) + qWarning("Device on '%s' unplugged while an operation is in progress.", + qPrintable(portName)); + device->close(); + } +} + +SymbianDevice::SymbianDevice(SymbianDeviceData *data) : + m_data(data) +{ +} + +SymbianDevice::SymbianDevice() : + m_data(new SymbianDeviceData) +{ +} +SymbianDevice::SymbianDevice(const SymbianDevice &rhs) : + m_data(rhs.m_data) +{ +} + +SymbianDevice &SymbianDevice::operator=(const SymbianDevice &rhs) +{ + if (this != &rhs) + m_data = rhs.m_data; + return *this; +} + +SymbianDevice::~SymbianDevice() +{ +} + +void SymbianDevice::forcedClose() +{ + m_data->forcedClose(); +} + +QString SymbianDevice::portName() const +{ + return m_data->portName; +} + +QString SymbianDevice::friendlyName() const +{ + return m_data->friendlyName; +} + +QString SymbianDevice::additionalInformation() const +{ + return m_data->additionalInformation; +} + +void SymbianDevice::setAdditionalInformation(const QString &a) +{ + m_data->additionalInformation = a; +} + +SymbianDevice::TrkDevicePtr SymbianDevice::acquireDevice() +{ + if (debug) + qDebug() << "SymbianDevice::acquireDevice" << m_data->portName + << "acquired: " << m_data->deviceAcquired << " open: " << isOpen(); + if (isNull() || m_data->deviceAcquired) + return TrkDevicePtr(); + if (m_data->device.isNull()) { + m_data->device = TrkDevicePtr(new trk::TrkDevice); + m_data->device->setPort(m_data->portName); + m_data->device->setSerialFrame(m_data->type == SerialPortCommunication); + } + m_data->deviceAcquired = true; + return m_data->device; +} + +void SymbianDevice::releaseDevice(TrkDevicePtr *ptr /* = 0 */) +{ + if (debug) + qDebug() << "SymbianDevice::releaseDevice" << m_data->portName + << " open: " << isOpen(); + if (m_data->deviceAcquired) { + if (m_data->device->isOpen()) + m_data->device->clearWriteQueue(); + // Release if a valid pointer was passed in. + if (ptr && !ptr->isNull()) { + ptr->data()->disconnect(); + *ptr = TrkDevicePtr(); + } + m_data->deviceAcquired = false; + } else { + qWarning("Internal error: Attempt to release device that is not acquired."); + } +} + +QString SymbianDevice::deviceDesc() const +{ + return m_data->deviceDesc; +} + +QString SymbianDevice::manufacturer() const +{ + return m_data->manufacturer; +} + +DeviceCommunicationType SymbianDevice::type() const +{ + return m_data->type; +} + +bool SymbianDevice::isNull() const +{ + return m_data->portName.isEmpty(); +} + +bool SymbianDevice::isOpen() const +{ + return m_data->isOpen(); +} + +QString SymbianDevice::toString() const +{ + QString rc; + QTextStream str(&rc); + format(str); + return rc; +} + +void SymbianDevice::format(QTextStream &str) const +{ + str << (m_data->type == BlueToothCommunication ? "Bluetooth: " : "Serial: ") + << m_data->portName; + if (!m_data->friendlyName.isEmpty()) { + str << " (" << m_data->friendlyName; + if (!m_data->deviceDesc.isEmpty()) + str << " / " << m_data->deviceDesc; + str << ')'; + } + if (!m_data->manufacturer.isEmpty()) + str << " [" << m_data->manufacturer << ']'; +} + +// Compare by port and friendly name +int SymbianDevice::compare(const SymbianDevice &rhs) const +{ + if (const int prc = m_data->portName.compare(rhs.m_data->portName)) + return prc; + if (const int frc = m_data->friendlyName.compare(rhs.m_data->friendlyName)) + return frc; + return 0; +} + +SYMBIANUTILS_EXPORT QDebug operator<<(QDebug d, const SymbianDevice &cd) +{ + d.nospace() << cd.toString(); + return d; +} + +// ------------- SymbianDeviceManagerPrivate +struct SymbianDeviceManagerPrivate { + SymbianDeviceManagerPrivate() : m_initialized(false), m_destroyReleaseMapper(0) {} + + bool m_initialized; + SymbianDeviceManager::SymbianDeviceList m_devices; + QSignalMapper *m_destroyReleaseMapper; +}; + +SymbianDeviceManager::SymbianDeviceManager(QObject *parent) : + QObject(parent), + d(new SymbianDeviceManagerPrivate) +{ +} + +SymbianDeviceManager::~SymbianDeviceManager() +{ + delete d; +} + +SymbianDeviceManager::SymbianDeviceList SymbianDeviceManager::devices() const +{ + ensureInitialized(); + return d->m_devices; +} + +QString SymbianDeviceManager::toString() const +{ + QString rc; + QTextStream str(&rc); + str << d->m_devices.size() << " devices:\n"; + const int count = d->m_devices.size(); + for (int i = 0; i < count; i++) { + str << '#' << i << ' '; + d->m_devices.at(i).format(str); + str << '\n'; + } + return rc; +} + +int SymbianDeviceManager::findByPortName(const QString &p) const +{ + ensureInitialized(); + const int count = d->m_devices.size(); + for (int i = 0; i < count; i++) + if (d->m_devices.at(i).portName() == p) + return i; + return -1; +} + +QString SymbianDeviceManager::friendlyNameForPort(const QString &port) const +{ + const int idx = findByPortName(port); + return idx == -1 ? QString() : d->m_devices.at(idx).friendlyName(); +} + +SymbianDeviceManager::TrkDevicePtr + SymbianDeviceManager::acquireDevice(const QString &port) +{ + ensureInitialized(); + 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 TrkDevicePtr(); + } + const TrkDevicePtr rc = d->m_devices[idx].acquireDevice(); + if (debug) + qDebug() << "SymbianDeviceManager::acquireDevice" << port << " returns " << !rc.isNull(); + return rc; +} + +void SymbianDeviceManager::update() +{ + update(true); +} + +void SymbianDeviceManager::releaseDevice(const QString &port) +{ + const int idx = findByPortName(port); + if (debug) + qDebug() << "SymbianDeviceManager::releaseDevice" << port << idx << sender(); + if (idx != -1) { + d->m_devices[idx].releaseDevice(); + } else { + qWarning("Attempt to release non-existing device %s.", qPrintable(port)); + } +} + +void SymbianDeviceManager::setAdditionalInformation(const QString &port, const QString &ai) +{ + const int idx = findByPortName(port); + if (idx != -1) + d->m_devices[idx].setAdditionalInformation(ai); +} + +void SymbianDeviceManager::ensureInitialized() const +{ + if (!d->m_initialized) // Flag is set in update() + const_cast<SymbianDeviceManager*>(this)->update(false); +} + +void SymbianDeviceManager::update(bool emitSignals) +{ + static int n = 0; + typedef SymbianDeviceList::iterator SymbianDeviceListIterator; + + if (debug) + qDebug(">SerialDeviceLister::update(#%d, signals=%d)\n%s", n++, int(emitSignals), + qPrintable(toString())); + + d->m_initialized = true; + // Get ordered new list + SymbianDeviceList newDevices = serialPorts() + blueToothDevices(); + if (newDevices.size() > 1) + qStableSort(newDevices.begin(), newDevices.end()); + if (d->m_devices == newDevices) { // Happy, nothing changed. + if (debug) + qDebug("<SerialDeviceLister::update: unchanged\n"); + return; + } + // 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 + if (!d->m_devices.isEmpty()) { + // Find deleted devices + for (SymbianDeviceListIterator oldIt = d->m_devices.begin(); oldIt != d->m_devices.end(); ) { + if (newDevices.contains(*oldIt)) { + ++oldIt; + } else { + SymbianDevice toBeDeleted = *oldIt; + toBeDeleted.forcedClose(); + oldIt = d->m_devices.erase(oldIt); + if (emitSignals) + emit deviceRemoved(toBeDeleted); + } + } + } + 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); + } + } + if (d->m_devices.size() > 1) + qStableSort(d->m_devices.begin(), d->m_devices.end()); + } + if (emitSignals) + emit updated(); + + if (debug) + qDebug("<SerialDeviceLister::update\n%s\n", qPrintable(toString())); +} + +SymbianDeviceManager::SymbianDeviceList SymbianDeviceManager::serialPorts() const +{ + SymbianDeviceList rc; +#ifdef Q_OS_WIN + const QSettings registry(REGKEY_CURRENT_CONTROL_SET, QSettings::NativeFormat); + const QString usbSerialRootKey = QLatin1String(USBSER) + QLatin1Char('/'); + const int count = registry.value(usbSerialRootKey + QLatin1String("Count")).toInt(); + for (int i = 0; i < count; ++i) { + QString driver = registry.value(usbSerialRootKey + QString::number(i)).toString(); + if (driver.contains(QLatin1String("JAVACOMM"))) { + driver.replace(QLatin1Char('\\'), QLatin1Char('/')); + const QString driverRootKey = QLatin1String("Enum/") + driver + QLatin1Char('/'); + if (debug > 1) + qDebug() << "SerialDeviceLister::serialPorts(): Checking " << i << count + << REGKEY_CURRENT_CONTROL_SET << usbSerialRootKey << driverRootKey; + QScopedPointer<SymbianDeviceData> device(new SymbianDeviceData); + device->type = SerialPortCommunication; + device->friendlyName = registry.value(driverRootKey + QLatin1String("FriendlyName")).toString(); + device->portName = registry.value(driverRootKey + QLatin1String("Device Parameters/PortName")).toString(); + device->deviceDesc = registry.value(driverRootKey + QLatin1String("DeviceDesc")).toString(); + device->manufacturer = registry.value(driverRootKey + QLatin1String("Mfg")).toString(); + rc.append(SymbianDevice(device.take())); + } + } +#endif + return rc; +} + +SymbianDeviceManager::SymbianDeviceList SymbianDeviceManager::blueToothDevices() const +{ + SymbianDeviceList rc; +#if defined(Q_OS_UNIX) && !defined(Q_OS_MAC) + // Bluetooth devices are created on connection. List the existing ones + // or at least the first one. + const QString prefix = QLatin1String(linuxBlueToothDeviceRootC); + const QString blueToothfriendlyFormat = QLatin1String("Bluetooth device (%1)"); + for (int d = 0; d < 4; d++) { + QScopedPointer<SymbianDeviceData> device(new SymbianDeviceData); + device->type = BlueToothCommunication; + device->portName = prefix + QString::number(d); + if (d == 0 || QFileInfo(device->portName).exists()) { + device->friendlyName = blueToothfriendlyFormat.arg(device->portName); + rc.push_back(SymbianDevice(device.take())); + } + } + // New kernel versions support /dev/ttyUSB0, /dev/ttyUSB1. Trk responds + // on the latter (usually), try first. + static const char *usbTtyDevices[] = { "/dev/ttyUSB1", "/dev/ttyUSB0" }; + const int usbTtyCount = sizeof(usbTtyDevices)/sizeof(const char *); + for (int d = 0; d < usbTtyCount; d++) { + const QString ttyUSBDevice = QLatin1String(usbTtyDevices[d]); + if (QFileInfo(ttyUSBDevice).exists()) { + SymbianDeviceData *device = new SymbianDeviceData; + device->type = SerialPortCommunication; + device->portName = ttyUSBDevice; + device->friendlyName = QString::fromLatin1("USB/Serial device (%1)").arg(device->portName); + rc.push_back(SymbianDevice(device)); + } + } +#endif + return rc; +} + +Q_GLOBAL_STATIC(SymbianDeviceManager, symbianDeviceManager) + +SymbianDeviceManager *SymbianDeviceManager::instance() +{ + return symbianDeviceManager(); +} + +SYMBIANUTILS_EXPORT QDebug operator<<(QDebug d, const SymbianDeviceManager &sdm) +{ + d.nospace() << sdm.toString(); + return d; +} + +} // namespace SymbianUtilsInternal diff --git a/src/runonphone/symbianutils/symbiandevicemanager.h b/src/runonphone/symbianutils/symbiandevicemanager.h new file mode 100644 index 000000000..b4edd7884 --- /dev/null +++ b/src/runonphone/symbianutils/symbiandevicemanager.h @@ -0,0 +1,178 @@ +/**************************************************************************** +** +** 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 SYMBIANDEVICEMANAGER_H +#define SYMBIANDEVICEMANAGER_H + +#include "symbianutils_global.h" + +#include <QtCore/QObject> +#include <QtCore/QExplicitlySharedDataPointer> +#include <QtCore/QSharedPointer> + +QT_BEGIN_NAMESPACE +class QDebug; +class QTextStream; +QT_END_NAMESPACE + +namespace trk { + class TrkDevice; +} + +namespace SymbianUtils { + +struct SymbianDeviceManagerPrivate; +class SymbianDeviceData; + +enum DeviceCommunicationType { + SerialPortCommunication = 0, + BlueToothCommunication = 1 +}; + +// 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 +// device being closed. +class SYMBIANUTILS_EXPORT SymbianDevice { + explicit SymbianDevice(SymbianDeviceData *data); + friend class SymbianDeviceManager; +public: + typedef QSharedPointer<trk::TrkDevice> TrkDevicePtr; + + SymbianDevice(); + SymbianDevice(const SymbianDevice &rhs); + SymbianDevice &operator=(const SymbianDevice &rhs); + ~SymbianDevice(); + int compare(const SymbianDevice &rhs) const; + + DeviceCommunicationType type() const; + bool isNull() const; + QString portName() const; + QString friendlyName() const; + 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. + QString deviceDesc() const; + QString manufacturer() const; + + void format(QTextStream &str) const; + QString toString() const; + +private: + void forcedClose(); + + QExplicitlySharedDataPointer<SymbianDeviceData> m_data; +}; + +SYMBIANUTILS_EXPORT QDebug operator<<(QDebug d, const SymbianDevice &); + +inline bool operator==(const SymbianDevice &d1, const SymbianDevice &d2) + { return d1.compare(d2) == 0; } +inline bool operator!=(const SymbianDevice &d1, const SymbianDevice &d2) + { return d1.compare(d2) != 0; } +inline bool operator<(const SymbianDevice &d1, const SymbianDevice &d2) + { return d1.compare(d2) < 0; } + +/* SymbianDeviceManager: Singleton that maintains a list of Symbian devices. + * and emits change signals. + * On Windows, the update slot must be connected to a [delayed] signal + * emitted from an event handler listening for WM_DEVICECHANGE. + * Device removal will result in the device being closed. */ +class SYMBIANUTILS_EXPORT SymbianDeviceManager : public QObject +{ + Q_OBJECT +public: + typedef QList<SymbianDevice> SymbianDeviceList; + typedef QSharedPointer<trk::TrkDevice> TrkDevicePtr; + + static const char *linuxBlueToothDeviceRootC; + + // Do not use this constructor, it is just public for Q_GLOBAL_STATIC + explicit SymbianDeviceManager(QObject *parent = 0); + virtual ~SymbianDeviceManager(); + + // Singleton access. + static SymbianDeviceManager *instance(); + + SymbianDeviceList devices() const; + QString toString() const; + + // Acquire a device for use. See releaseDevice(). + TrkDevicePtr acquireDevice(const QString &port); + + int findByPortName(const QString &p) const; + QString friendlyNameForPort(const QString &port) const; + +public slots: + void update(); + // Relase a device, make it available for further use. + void releaseDevice(const QString &port); + void setAdditionalInformation(const QString &port, const QString &ai); + +signals: + void deviceRemoved(const SymbianUtils::SymbianDevice &d); + void deviceAdded(const SymbianUtils::SymbianDevice &d); + void updated(); + +private: + void ensureInitialized() const; + void update(bool emitSignals); + SymbianDeviceList serialPorts() const; + SymbianDeviceList blueToothDevices() const; + + SymbianDeviceManagerPrivate *d; +}; + +SYMBIANUTILS_EXPORT QDebug operator<<(QDebug d, const SymbianDeviceManager &); + +} // namespace SymbianUtils + +#endif // SYMBIANDEVICEMANAGER_H diff --git a/src/runonphone/symbianutils/symbianutils.pri b/src/runonphone/symbianutils/symbianutils.pri new file mode 100644 index 000000000..f07e494ed --- /dev/null +++ b/src/runonphone/symbianutils/symbianutils.pri @@ -0,0 +1,35 @@ +INCLUDEPATH *= $$PWD + +QT += network + +# Input +HEADERS += $$PWD/symbianutils_global.h \ + $$PWD/callback.h \ + $$PWD/trkutils.h \ + $$PWD/trkutils_p.h \ + $$PWD/trkdevice.h \ + $$PWD/launcher.h \ + $$PWD/bluetoothlistener.h \ + $$PWD/communicationstarter.h \ + $$PWD/symbiandevicemanager.h \ + $$PWD/tcftrkdevice.h \ + $$PWD/tcftrkmessage.h \ + $$PWD/json.h + +SOURCES += $$PWD/trkutils.cpp \ + $$PWD/trkdevice.cpp \ + $$PWD/launcher.cpp \ + $$PWD/bluetoothlistener.cpp \ + $$PWD/communicationstarter.cpp \ + $$PWD/symbiandevicemanager.cpp \ + $$PWD/tcftrkdevice.cpp \ + $$PWD/tcftrkmessage.cpp \ + $$PWD/json.cpp + +# Tests/trklauncher is a console application +contains(QT, gui) { + HEADERS += $$PWD/bluetoothlistener_gui.h + SOURCES += $$PWD/bluetoothlistener_gui.cpp +} else { + message(Trk: Console ...) +} diff --git a/src/runonphone/symbianutils/symbianutils_global.h b/src/runonphone/symbianutils/symbianutils_global.h new file mode 100644 index 000000000..d7acabffd --- /dev/null +++ b/src/runonphone/symbianutils/symbianutils_global.h @@ -0,0 +1,55 @@ +/**************************************************************************** +** +** 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 SYMBIANUTILS_GLOBAL_H +#define SYMBIANUTILS_GLOBAL_H + +#include <QtCore/qglobal.h> + +#if defined(SYMBIANUTILS_BUILD_LIB) +# define SYMBIANUTILS_EXPORT Q_DECL_EXPORT +#elif defined(SYMBIANUTILS_BUILD_STATIC_LIB) || defined(SYMBIANUTILS_INCLUDE_PRI) +# define SYMBIANUTILS_EXPORT +#else +# define SYMBIANUTILS_EXPORT Q_DECL_IMPORT +#endif + +#endif // SYMBIANUTILS_GLOBAL_H diff --git a/src/runonphone/symbianutils/tcftrkdevice.cpp b/src/runonphone/symbianutils/tcftrkdevice.cpp new file mode 100644 index 000000000..5b923ee90 --- /dev/null +++ b/src/runonphone/symbianutils/tcftrkdevice.cpp @@ -0,0 +1,929 @@ +/**************************************************************************** +** +** 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 "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/src/runonphone/symbianutils/tcftrkdevice.h b/src/runonphone/symbianutils/tcftrkdevice.h new file mode 100644 index 000000000..d42f3f3cb --- /dev/null +++ b/src/runonphone/symbianutils/tcftrkdevice.h @@ -0,0 +1,295 @@ +/**************************************************************************** +** +** 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 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/src/runonphone/symbianutils/tcftrkmessage.cpp b/src/runonphone/symbianutils/tcftrkmessage.cpp new file mode 100644 index 000000000..8d62dece2 --- /dev/null +++ b/src/runonphone/symbianutils/tcftrkmessage.cpp @@ -0,0 +1,562 @@ +/**************************************************************************** +** +** 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 "tcftrkmessage.h" +#include "json.h" + +#include <QtCore/QString> +#include <QtCore/QTextStream> + +// Names matching the enum +static const char *serviceNamesC[] = +{ "Locator", "RunControl", "Processes", "Memory", "Settings", "Breakpoints", + "Registers", "SimpleRegisters", + "UnknownService"}; + +namespace tcftrk { + +SYMBIANUTILS_EXPORT QString joinByteArrays(const QVector<QByteArray> &a, char sep) +{ + QString rc; + const int count = a.size(); + for (int i = 0; i < count; i++) { + if (i) + rc += QLatin1Char(sep); + rc += QString::fromUtf8(a.at(i)); + } + return rc; +} + +static inline bool jsonToBool(const JsonValue& js) +{ + return js.type() == JsonValue::Boolean && js.data() == "true"; +} + +SYMBIANUTILS_EXPORT const char *serviceName(Services s) +{ + return serviceNamesC[s]; +} + +SYMBIANUTILS_EXPORT Services serviceFromName(const char *n) +{ + const int count = sizeof(serviceNamesC)/sizeof(char *); + for (int i = 0; i < count; i++) + if (!qstrcmp(serviceNamesC[i], n)) + return static_cast<Services>(i); + return UnknownService; +} + +SYMBIANUTILS_EXPORT QString formatData(const QByteArray &a) +{ + const int columns = 16; + QString rc; + QTextStream str(&rc); + str.setIntegerBase(16); + str.setPadChar(QLatin1Char('0')); + const unsigned char *start = reinterpret_cast<const unsigned char *>(a.constData()); + const unsigned char *end = start + a.size(); + for (const unsigned char *p = start; p < end ; ) { + str << "0x"; + str.setFieldWidth(4); + str << (p - start); + str.setFieldWidth(0); + str << ' '; + QString asc; + int c = 0; + for ( ; c < columns && p < end; c++, p++) { + const unsigned u = *p; + str.setFieldWidth(2); + str << u; + str.setFieldWidth(0); + str << ' '; + switch (u) { + case '\n': + asc += QLatin1String("\\n"); + break; + case '\r': + asc += QLatin1String("\\r"); + break; + case '\t': + asc += QLatin1String("\\t"); + break; + default: + if (u >= 32 && u < 128) { + asc += QLatin1Char(' '); + asc += QLatin1Char(u); + } else { + asc += QLatin1String(" ."); + } + break; + } + } + if (const int remainder = columns - c) + str << QString(3 * remainder, QLatin1Char(' ')); + str << ' ' << asc << '\n'; + } + return rc; +} + +// ----------- RunControlContext +RunControlContext::RunControlContext() : + flags(0), resumeFlags(0) +{ +} + +void RunControlContext::clear() +{ + flags =0; + resumeFlags = 0; + id.clear(); + osid.clear(); + parentId.clear(); +} + +RunControlContext::Type RunControlContext::typeFromTcfId(const QByteArray &id) +{ + // "p12" or "p12.t34"? + return id.contains(".t") ? Thread : Process; +} + +unsigned RunControlContext::processId() const +{ + return processIdFromTcdfId(id); +} + +unsigned RunControlContext::threadId() const +{ + return threadIdFromTcdfId(id); +} + +unsigned RunControlContext::processIdFromTcdfId(const QByteArray &id) +{ + // Cut out process id from "p12" or "p12.t34"? + if (!id.startsWith('p')) + return 0; + const int dotPos = id.indexOf('.'); + const int pLen = dotPos == -1 ? id.size() : dotPos; + return id.mid(1, pLen - 1).toUInt(); +} + +unsigned RunControlContext::threadIdFromTcdfId(const QByteArray &id) +{ + const int tPos = id.indexOf(".t"); + return tPos != -1 ? id.mid(tPos + 2).toUInt() : uint(0); +} + +QByteArray RunControlContext::tcfId(unsigned processId, unsigned threadId /* = 0 */) +{ + QByteArray rc("p"); + rc += QByteArray::number(processId); + if (threadId) { + rc += ".t"; + rc += QByteArray::number(threadId); + } + return rc; +} + +RunControlContext::Type RunControlContext::type() const +{ + return RunControlContext::typeFromTcfId(id); +} + +bool RunControlContext::parse(const JsonValue &val) +{ + clear(); + if (val.type() != JsonValue::Object) + return false; + foreach(const JsonValue &c, val.children()) { + if (c.name() == "ID") { + id = c.data(); + } else if (c.name() == "OSID") { + osid = c.data(); + } else if (c.name() == "ParentID") { + parentId = c.data(); + } else if (c.name() == "IsContainer") { + if (jsonToBool(c)) + flags |= Container; + } else if (c.name() == "CanTerminate") { + if (jsonToBool(c)) + flags |= CanTerminate; + } else if (c.name() == "CanResume") { + resumeFlags = c.data().toUInt(); + } else if (c.name() == "HasState") { + if (jsonToBool(c)) + flags |= HasState; + } else if (c.name() == "CanSuspend") { + if (jsonToBool(c)) + flags |= CanSuspend; + } + } + return true; +} + +QString RunControlContext::toString() const +{ + QString rc; + QTextStream str(&rc); + format(str); + return rc; +} + +void RunControlContext::format(QTextStream &str) const +{ + str << " id='" << id << "' osid='" << osid + << "' parentId='" << parentId <<"' "; + if (flags & Container) + str << "[container] "; + if (flags & HasState) + str << "[has state] "; + if (flags & CanSuspend) + str << "[can suspend] "; + if (flags & CanSuspend) + str << "[can terminate] "; + str.setIntegerBase(16); + str << " resume_flags: 0x" << resumeFlags; + str.setIntegerBase(10); +} + +// ------ ModuleLoadEventInfo +ModuleLoadEventInfo::ModuleLoadEventInfo() : + loaded(false), codeAddress(0), dataAddress(0), requireResume(false) +{ +} + +void ModuleLoadEventInfo::clear() +{ + loaded = requireResume = false; + codeAddress = dataAddress =0; +} + +bool ModuleLoadEventInfo::parse(const JsonValue &val) +{ + clear(); + if (val.type() != JsonValue::Object) + return false; + foreach(const JsonValue &c, val.children()) { + if (c.name() == "Name") { + name = c.data(); + } else if (c.name() == "File") { + file = c.data(); + } else if (c.name() == "CodeAddress") { + codeAddress = c.data().toULongLong(); + } else if (c.name() == "DataAddress") { + dataAddress = c.data().toULongLong(); + } else if (c.name() == "Loaded") { + loaded = jsonToBool(c); + } else if (c.name() == "RequireResume") { + requireResume =jsonToBool(c); + } + } + return true; +} +void ModuleLoadEventInfo::format(QTextStream &str) const +{ + str << "name='" << name << "' file='" << file << "' " << + (loaded ? "[loaded] " : "[not loaded] "); + if (requireResume) + str << "[requires resume] "; + str.setIntegerBase(16); + str << " code: 0x" << codeAddress << " data: 0x" << dataAddress; + str.setIntegerBase(10); +} + +// ---------------------- Breakpoint + +// Types matching enum +static const char *breakPointTypesC[] = {"Software", "Hardware", "Auto"}; + +Breakpoint::Breakpoint(quint64 loc) : + type(Auto), enabled(true), ignoreCount(0), location(loc), size(1), thumb(true) +{ + if (loc) + id = idFromLocation(location); +} + +void Breakpoint::setContextId(unsigned processId, unsigned threadId) +{ + contextIds = QVector<QByteArray>(1, RunControlContext::tcfId(processId, threadId)); +} + +QByteArray Breakpoint::idFromLocation(quint64 loc) +{ + return QByteArray("BP_0x") + QByteArray::number(loc, 16); +} + +QString Breakpoint::toString() const +{ + QString rc; + QTextStream str(&rc); + str.setIntegerBase(16); + str << "Breakpoint '" << id << "' " << breakPointTypesC[type] << " for contexts '" + << joinByteArrays(contextIds, ',') << "' at 0x" << location; + str.setIntegerBase(10); + str << " size " << size; + if (enabled) + str << " [enabled]"; + if (thumb) + str << " [thumb]"; + if (ignoreCount) + str << " IgnoreCount " << ignoreCount; + return rc; +} + +JsonInputStream &operator<<(JsonInputStream &str, const Breakpoint &b) +{ + if (b.contextIds.isEmpty()) + qWarning("tcftrk::Breakpoint: No context ids specified"); + + str << '{' << "ID" << ':' << QString::fromUtf8(b.id) << ',' + << "BreakpointType" << ':' << breakPointTypesC[b.type] << ',' + << "Enabled" << ':' << b.enabled << ',' + << "IgnoreCount" << ':' << b.ignoreCount << ',' + << "ContextIds" << ':' << b.contextIds << ',' + << "Location" << ':' << QString::number(b.location) << ',' + << "Size" << ':' << b.size << ',' + << "THUMB_BREAKPOINT" << ':' << b.thumb + << '}'; + return str; +} + +// --- Events +TcfTrkEvent::TcfTrkEvent(Type type) : m_type(type) +{ +} + +TcfTrkEvent::~TcfTrkEvent() +{ +} + +TcfTrkEvent::Type TcfTrkEvent::type() const +{ + return m_type; +} + +QString TcfTrkEvent::toString() const +{ + return QString(); +} + +static const char sharedLibrarySuspendReasonC[] = "Shared Library"; + +TcfTrkEvent *TcfTrkEvent::parseEvent(Services s, const QByteArray &nameBA, const QVector<JsonValue> &values) +{ + switch (s) { + case LocatorService: + if (nameBA == "Hello" && values.size() == 1 && values.front().type() == JsonValue::Array) { + QStringList services; + foreach (const JsonValue &jv, values.front().children()) + services.push_back(QString::fromUtf8(jv.data())); + return new TcfTrkLocatorHelloEvent(services); + } + break; + case RunControlService: + if (values.empty()) + return 0; + // "id/PC/Reason/Data" + if (nameBA == "contextSuspended" && values.size() == 4) { + const QByteArray idBA = values.at(0).data(); + const quint64 pc = values.at(1).data().toULongLong(); + const QByteArray reasonBA = values.at(2).data(); + // 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 TcfTrkRunControlContextSuspendedEvent(idBA, reasonBA, pc); + } // "contextSuspended" + if (nameBA == "contextAdded") + return TcfTrkRunControlContextAddedEvent::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); + } + break; + default: + break; + } + return 0; +} + +// -------------- TcfTrkServiceHelloEvent +TcfTrkLocatorHelloEvent::TcfTrkLocatorHelloEvent(const QStringList &s) : + TcfTrkEvent(LocatorHello), + m_services(s) +{ +} + +QString TcfTrkLocatorHelloEvent::toString() const +{ + return QLatin1String("ServiceHello: ") + m_services.join(QLatin1String(", ")); +} + +// -------------- TcfTrkIdEvent +TcfTrkIdEvent::TcfTrkIdEvent(Type t, const QByteArray &id) : + TcfTrkEvent(t), m_id(id) +{ +} + +// ---------- TcfTrkIdsEvent +TcfTrkIdsEvent::TcfTrkIdsEvent(Type t, const QVector<QByteArray> &ids) : + TcfTrkEvent(t), m_ids(ids) +{ +} + +QString TcfTrkIdsEvent::joinedIdString(const char sep) const +{ + return joinByteArrays(m_ids, sep); +} + +// ---------------- TcfTrkRunControlContextAddedEvent +TcfTrkRunControlContextAddedEvent::TcfTrkRunControlContextAddedEvent(const RunControlContexts &c) : + TcfTrkEvent(RunControlContextAdded), m_contexts(c) +{ +} + +TcfTrkRunControlContextAddedEvent + *TcfTrkRunControlContextAddedEvent::parseEvent(const QVector<JsonValue> &values) +{ + // Parse array of contexts + if (values.size() < 1 || values.front().type() != JsonValue::Array) + return 0; + + RunControlContexts contexts; + foreach (const JsonValue &v, values.front().children()) { + RunControlContext context; + if (context.parse(v)) + contexts.push_back(context); + } + return new TcfTrkRunControlContextAddedEvent(contexts); +} + +QString TcfTrkRunControlContextAddedEvent::toString() const +{ + QString rc; + QTextStream str(&rc); + str << "RunControl: " << m_contexts.size() << " context(s) " + << (type() == RunControlContextAdded ? "added" : "removed") + << '\n'; + foreach (const RunControlContext &c, m_contexts) { + c.format(str); + str << '\n'; + } + return rc; +} + +// --------------- TcfTrkRunControlContextRemovedEvent +TcfTrkRunControlContextRemovedEvent::TcfTrkRunControlContextRemovedEvent(const QVector<QByteArray> &ids) : + TcfTrkIdsEvent(RunControlContextRemoved, ids) +{ +} + +QString TcfTrkRunControlContextRemovedEvent::toString() const +{ + return QLatin1String("RunControl: Removed contexts '") + joinedIdString() + ("'."); +} + +// --------------- TcfTrkRunControlContextSuspendedEvent +TcfTrkRunControlContextSuspendedEvent::TcfTrkRunControlContextSuspendedEvent(const QByteArray &id, + const QByteArray &reason, + quint64 pc) : + TcfTrkIdEvent(RunControlSuspended, id), m_pc(pc), m_reason(reason) +{ +} + +TcfTrkRunControlContextSuspendedEvent::TcfTrkRunControlContextSuspendedEvent(Type t, + const QByteArray &id, + const QByteArray &reason, + quint64 pc) : + TcfTrkIdEvent(t, id), m_pc(pc), m_reason(reason) +{ +} + +void TcfTrkRunControlContextSuspendedEvent::format(QTextStream &str) const +{ + str.setIntegerBase(16); + str << "RunControl: '" << idString() << "' suspended at 0x" + << m_pc << ": '" << m_reason << "'."; + str.setIntegerBase(10); +} + +QString TcfTrkRunControlContextSuspendedEvent::toString() const +{ + QString rc; + QTextStream str(&rc); + format(str); + return rc; +} + +TcfTrkRunControlContextSuspendedEvent::Reason TcfTrkRunControlContextSuspendedEvent::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")) + return Crash; + return Other; +} + +TcfTrkRunControlModuleLoadContextSuspendedEvent::TcfTrkRunControlModuleLoadContextSuspendedEvent(const QByteArray &id, + const QByteArray &reason, + quint64 pc, + const ModuleLoadEventInfo &mi) : + TcfTrkRunControlContextSuspendedEvent(RunControlModuleLoadSuspended, id, reason, pc), + m_mi(mi) +{ +} + +QString TcfTrkRunControlModuleLoadContextSuspendedEvent::toString() const +{ + QString rc; + QTextStream str(&rc); + TcfTrkRunControlContextSuspendedEvent::format(str); + str << ' '; + m_mi.format(str); + return rc; +} + + +} // namespace tcftrk diff --git a/src/runonphone/symbianutils/tcftrkmessage.h b/src/runonphone/symbianutils/tcftrkmessage.h new file mode 100644 index 000000000..f2c8022e3 --- /dev/null +++ b/src/runonphone/symbianutils/tcftrkmessage.h @@ -0,0 +1,296 @@ +/**************************************************************************** +** +** 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 TRCFTRKMESSAGE_H +#define TRCFTRKMESSAGE_H + +#include "symbianutils_global.h" + +#include <QtCore/QStringList> +#include <QtCore/QVector> + +QT_BEGIN_NAMESPACE +class QTextStream; +QT_END_NAMESPACE + +namespace tcftrk { + +class JsonValue; +class JsonInputStream; + +enum Services { + LocatorService, + RunControlService, + ProcessesService, + MemoryService, + SettingsService, // non-standard, trk specific + BreakpointsService, + RegistersService, + SimpleRegistersService, // non-standard, trk specific + UnknownService +}; // Note: Check string array 'serviceNamesC' of same size when modifying this. + +// Modes of RunControl/'Resume' (see EDF documentation). +// As of 24.6.2010, RM_RESUME, RM_STEP_OVER, RM_STEP_INTO, +// RM_STEP_OVER_RANGE, RM_STEP_INTO_RANGE are supported with +// RANG_START/RANGE_END parameters. +enum RunControlResumeMode { + RM_RESUME = 0, + RM_STEP_OVER = 1, RM_STEP_INTO = 2, + RM_STEP_OVER_LINE = 3, RM_STEP_INTO_LINE = 4, + RM_STEP_OUT = 5, RM_REVERSE_RESUME = 6, + RM_REVERSE_STEP_OVER = 7, RM_REVERSE_STEP_INTO = 8, + RM_REVERSE_STEP_OVER_LINE = 9, RM_REVERSE_STEP_INTO_LINE = 10, + RM_REVERSE_STEP_OUT = 11, RM_STEP_OVER_RANGE = 12, + RM_STEP_INTO_RANGE = 13, RM_REVERSE_STEP_OVER_RANGE = 14, + RM_REVERSE_STEP_INTO_RANGE = 15 +}; + +SYMBIANUTILS_EXPORT const char *serviceName(Services s); +SYMBIANUTILS_EXPORT Services serviceFromName(const char *); + +// Debug helpers +SYMBIANUTILS_EXPORT QString formatData(const QByteArray &a); +SYMBIANUTILS_EXPORT QString joinByteArrays(const QVector<QByteArray> &a, char sep = ','); + +// Context used in 'RunControl contextAdded' events and in reply +// to 'Processes start'. Could be thread or process. +struct SYMBIANUTILS_EXPORT RunControlContext { + enum Flags { + Container = 0x1, HasState = 0x2, CanSuspend = 0x4, + CanTerminate = 0x8 + }; + enum Type { Process, Thread }; + + RunControlContext(); + Type type() const; + unsigned processId() const; + unsigned threadId() const; + + void clear(); + bool parse(const JsonValue &v); + void format(QTextStream &str) const; + QString toString() const; + + // Helper for converting the TCF ids ("p12" or "p12.t34") + static Type typeFromTcfId(const QByteArray &id); + static unsigned processIdFromTcdfId(const QByteArray &id); + static unsigned threadIdFromTcdfId(const QByteArray &id); + static QByteArray tcfId(unsigned processId, unsigned threadId = 0); + + unsigned flags; + unsigned resumeFlags; + QByteArray id; // "p434.t699" + QByteArray osid; // Non-standard: Process or thread id + QByteArray parentId; // Parent process id of a thread. +}; + +// Module load information occurring with 'RunControl contextSuspended' events +struct SYMBIANUTILS_EXPORT ModuleLoadEventInfo { + ModuleLoadEventInfo(); + void clear(); + bool parse(const JsonValue &v); + void format(QTextStream &str) const; + + QByteArray name; + QByteArray file; + bool loaded; + quint64 codeAddress; + quint64 dataAddress; + bool requireResume; +}; + +// Breakpoint as supported by TcfTrk source June 2010 +// TODO: Add watchpoints,etc once they are implemented +struct SYMBIANUTILS_EXPORT Breakpoint { + enum Type { Software, Hardware, Auto }; + + explicit Breakpoint(quint64 loc = 0); + void setContextId(unsigned processId, unsigned threadId = 0); + QString toString() const; + + static QByteArray idFromLocation(quint64 loc); // Automagically determine from location + + Type type; + bool enabled; + int ignoreCount; + QVector<QByteArray> contextIds; // Process or thread ids. + QByteArray id; // Id of the breakpoint; + quint64 location; + unsigned size; + bool thumb; +}; + +SYMBIANUTILS_EXPORT JsonInputStream &operator<<(JsonInputStream &str, const Breakpoint &b); + +// Event hierarchy +class SYMBIANUTILS_EXPORT TcfTrkEvent { + Q_DISABLE_COPY(TcfTrkEvent) +public: + enum Type { None, + LocatorHello, + RunControlContextAdded, + RunControlContextRemoved, + RunControlSuspended, + RunControlBreakpointSuspended, + RunControlModuleLoadSuspended, + RunControlResumed + }; + + virtual ~TcfTrkEvent(); + + Type type() const; + virtual QString toString() const; + + static TcfTrkEvent *parseEvent(Services s, const QByteArray &name, const QVector<JsonValue> &val); + +protected: + explicit TcfTrkEvent(Type type = None); + +private: + const Type m_type; +}; + +// ServiceHello +class SYMBIANUTILS_EXPORT TcfTrkLocatorHelloEvent : public TcfTrkEvent { +public: + explicit TcfTrkLocatorHelloEvent(const QStringList &); + + const QStringList &services() { return m_services; } + virtual QString toString() const; + +private: + QStringList m_services; +}; + +// Base for events that just have one id as parameter +// (simple suspend) +class SYMBIANUTILS_EXPORT TcfTrkIdEvent : public TcfTrkEvent { +protected: + explicit TcfTrkIdEvent(Type t, const QByteArray &id); +public: + QByteArray id() const { return m_id; } + QString idString() const { return QString::fromUtf8(m_id); } + +private: + const QByteArray m_id; +}; + +// Base for events that just have some ids as parameter +// (context removed) +class SYMBIANUTILS_EXPORT TcfTrkIdsEvent : public TcfTrkEvent { +protected: + explicit TcfTrkIdsEvent(Type t, const QVector<QByteArray> &ids); + +public: + QVector<QByteArray> ids() const { return m_ids; } + QString joinedIdString(const char sep = ',') const; + +private: + const QVector<QByteArray> m_ids; +}; + +// RunControlContextAdded +class SYMBIANUTILS_EXPORT TcfTrkRunControlContextAddedEvent : public TcfTrkEvent { +public: + typedef QVector<RunControlContext> RunControlContexts; + + explicit TcfTrkRunControlContextAddedEvent(const RunControlContexts &c); + + const RunControlContexts &contexts() const { return m_contexts; } + virtual QString toString() const; + + static TcfTrkRunControlContextAddedEvent *parseEvent(const QVector<JsonValue> &val); + +private: + const RunControlContexts m_contexts; +}; + +// RunControlContextRemoved +class SYMBIANUTILS_EXPORT TcfTrkRunControlContextRemovedEvent : public TcfTrkIdsEvent { +public: + explicit TcfTrkRunControlContextRemovedEvent(const QVector<QByteArray> &id); + virtual QString toString() const; +}; + +// Simple RunControlContextSuspended (process/thread) +class SYMBIANUTILS_EXPORT TcfTrkRunControlContextSuspendedEvent : public TcfTrkIdEvent { +public: + enum Reason { BreakPoint, ModuleLoad, Crash, Other } ; + + explicit TcfTrkRunControlContextSuspendedEvent(const QByteArray &id, + const QByteArray &reason, + quint64 pc = 0); + virtual QString toString() const; + + quint64 pc() const { return m_pc; } + QByteArray reasonID() const { return m_reason; } + Reason reason() const; + +protected: + explicit TcfTrkRunControlContextSuspendedEvent(Type t, + const QByteArray &id, + const QByteArray &reason, + quint64 pc = 0); + void format(QTextStream &str) const; + +private: + const quint64 m_pc; + const QByteArray m_reason; +}; + +// RunControlContextSuspended due to module load +class SYMBIANUTILS_EXPORT TcfTrkRunControlModuleLoadContextSuspendedEvent : public TcfTrkRunControlContextSuspendedEvent { +public: + explicit TcfTrkRunControlModuleLoadContextSuspendedEvent(const QByteArray &id, + const QByteArray &reason, + quint64 pc, + const ModuleLoadEventInfo &mi); + + virtual QString toString() const; + const ModuleLoadEventInfo &info() const { return m_mi; } + +private: + const ModuleLoadEventInfo m_mi; +}; + +} // namespace tcftrk +#endif // TRCFTRKMESSAGE_H diff --git a/src/runonphone/symbianutils/trkdevice.cpp b/src/runonphone/symbianutils/trkdevice.cpp new file mode 100644 index 000000000..2825b5f9b --- /dev/null +++ b/src/runonphone/symbianutils/trkdevice.cpp @@ -0,0 +1,1184 @@ +/**************************************************************************** +** +** 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 "trkdevice.h" +#include "trkutils.h" +#include "trkutils_p.h" + +#include <QtCore/QString> +#include <QtCore/QDebug> +#include <QtCore/QQueue> +#include <QtCore/QHash> +#include <QtCore/QMap> +#include <QtCore/QThread> +#include <QtCore/QMutex> +#include <QtCore/QWaitCondition> +#include <QtCore/QSharedPointer> +#include <QtCore/QScopedPointer> +#include <QtCore/QMetaType> + +#ifdef Q_OS_WIN +# include <windows.h> +#else +# include <QtCore/QFile> + +# include <stdio.h> +# include <sys/ioctl.h> +# include <sys/types.h> +# include <termios.h> +# include <errno.h> +# include <string.h> +# include <unistd.h> +/* Required headers for select() according to POSIX.1-2001 */ +# include <sys/select.h> +/* Required headers for select() according to earlier standards: + #include <sys/time.h> + #include <sys/types.h> + #include <unistd.h> +*/ +#endif + +#ifdef Q_OS_WIN + +// Format windows error from GetLastError() value: +// TODO: Use the one provided by the utils lib. +QString winErrorMessage(unsigned long error) +{ + 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; +} + +#endif + +enum { verboseTrk = 0 }; + +static inline QString msgAccessingClosedDevice(const QString &msg) +{ + return QString::fromLatin1("Error: Attempt to access device '%1', which is closed.").arg(msg); +} + +namespace trk { + +/////////////////////////////////////////////////////////////////////// +// +// TrkMessage +// +/////////////////////////////////////////////////////////////////////// + +/* A message to be send to TRK, triggering a callback on receipt + * of the answer. */ +struct TrkMessage +{ + explicit TrkMessage(byte code = 0u, byte token = 0u, + TrkCallback callback = TrkCallback()); + + byte code; + byte token; + QByteArray data; + QVariant cookie; + TrkCallback callback; +}; + +TrkMessage::TrkMessage(byte c, byte t, TrkCallback cb) : + code(c), + token(t), + callback(cb) +{ +} + +QDebug operator<<(QDebug d, const TrkMessage &msg) +{ + return d << "Message: Code: " << msg.code + << " Token: " << msg.token << " " << msg.data.toHex(); +} + +} // namespace trk + +Q_DECLARE_METATYPE(trk::TrkMessage) +Q_DECLARE_METATYPE(trk::TrkResult) + +namespace trk { + +/////////////////////////////////////////////////////////////////////// +// +// TrkWriteQueue: Mixin class that manages a write queue of Trk messages. +// pendingMessage()/notifyWriteResult() should be called from a worked/timer +// that writes the messages. The class does not take precautions for multithreading. +// A no-op message is simply taken off the queue. The calling class +// can use the helper invokeNoopMessage() to trigger its callback. +// +/////////////////////////////////////////////////////////////////////// + +class TrkWriteQueue +{ + Q_DISABLE_COPY(TrkWriteQueue) +public: + explicit TrkWriteQueue(); + void clear(); + + // Enqueue messages. + void queueTrkMessage(byte code, TrkCallback callback, + const QByteArray &data, const QVariant &cookie); + void queueTrkInitialPing(); + + // Call this from the device read notification with the results. + void slotHandleResult(const TrkResult &result, QMutex *mutex = 0); + + // pendingMessage() can be called periodically in a timer to retrieve + // the pending messages to be sent. + enum PendingMessageResult { + NoMessage, // No message in queue. + PendingMessage, /* There is a queued message. The calling class + * can write it out and use notifyWriteResult() + * to notify about the result. */ + NoopMessageDequeued // A no-op message has been dequeued. see invokeNoopMessage(). + }; + + PendingMessageResult pendingMessage(TrkMessage *message); + // Notify the queue about the success of the write operation + // after taking the pendingMessage off. + enum WriteResult { + WriteOk, + WriteFailedDiscard, // Discard failed message + WriteFailedKeep, // Keep failed message + }; + void notifyWriteResult(WriteResult ok); + + // Helper function that invokes the callback of a no-op message + static void invokeNoopMessage(trk::TrkMessage); + +private: + typedef QMap<byte, TrkMessage> TokenMessageMap; + + byte nextTrkWriteToken(); + + byte m_trkWriteToken; + QQueue<TrkMessage> m_trkWriteQueue; + TokenMessageMap m_writtenTrkMessages; + bool m_trkWriteBusy; +}; + +TrkWriteQueue::TrkWriteQueue() : + m_trkWriteToken(0), + m_trkWriteBusy(false) +{ +} + +void TrkWriteQueue::clear() +{ + m_trkWriteToken = 0; + m_trkWriteBusy = false; + m_trkWriteQueue.clear(); + const int discarded = m_writtenTrkMessages.size(); + m_writtenTrkMessages.clear(); + if (verboseTrk) + qDebug() << "TrkWriteQueue::clear: discarded " << discarded; +} + +byte TrkWriteQueue::nextTrkWriteToken() +{ + ++m_trkWriteToken; + if (m_trkWriteToken == 0) + ++m_trkWriteToken; + if (verboseTrk) + qDebug() << "nextTrkWriteToken:" << m_trkWriteToken; + return m_trkWriteToken; +} + +void TrkWriteQueue::queueTrkMessage(byte code, TrkCallback callback, + const QByteArray &data, const QVariant &cookie) +{ + const byte token = code == TRK_WRITE_QUEUE_NOOP_CODE ? + byte(0) : nextTrkWriteToken(); + TrkMessage msg(code, token, callback); + msg.data = data; + msg.cookie = cookie; + m_trkWriteQueue.append(msg); +} + +TrkWriteQueue::PendingMessageResult TrkWriteQueue::pendingMessage(TrkMessage *message) +{ + // Invoked from timer, try to flush out message queue + if (m_trkWriteBusy || m_trkWriteQueue.isEmpty()) + return NoMessage; + // Handle the noop message, just invoke CB in slot (ower thread) + if (m_trkWriteQueue.front().code == TRK_WRITE_QUEUE_NOOP_CODE) { + *message = m_trkWriteQueue.dequeue(); + return NoopMessageDequeued; + } + // Insert into map fir answers (as reading threads might get an + // answer before notifyWriteResult(true)) is called. + *message = m_trkWriteQueue.front(); + m_writtenTrkMessages.insert(message->token, *message); + m_trkWriteBusy = true; + return PendingMessage; +} + +void TrkWriteQueue::invokeNoopMessage(trk::TrkMessage noopMessage) +{ + TrkResult result; + result.code = noopMessage.code; + result.token = noopMessage.token; + result.data = noopMessage.data; + result.cookie = noopMessage.cookie; + noopMessage.callback(result); +} + +void TrkWriteQueue::notifyWriteResult(WriteResult wr) +{ + // On success, dequeue message and await result + const byte token = m_trkWriteQueue.front().token; + switch (wr) { + case WriteOk: + m_trkWriteQueue.dequeue(); + break; + case WriteFailedKeep: + case WriteFailedDiscard: + m_writtenTrkMessages.remove(token); + m_trkWriteBusy = false; + if (wr == WriteFailedDiscard) + m_trkWriteQueue.dequeue(); + break; + } +} + +void TrkWriteQueue::slotHandleResult(const TrkResult &result, QMutex *mutex) +{ + // Find which request the message belongs to and invoke callback + // if ACK or on NAK if desired. + if (mutex) + mutex->lock(); + m_trkWriteBusy = false; + const TokenMessageMap::iterator it = m_writtenTrkMessages.find(result.token); + if (it == m_writtenTrkMessages.end()) { + if (mutex) + mutex->unlock(); + return; + } + TrkCallback callback = it.value().callback; + const QVariant cookie = it.value().cookie; + m_writtenTrkMessages.erase(it); + if (mutex) + mutex->unlock(); + // Invoke callback + if (callback) { + TrkResult result1 = result; + result1.cookie = cookie; + callback(result1); + } +} + +void TrkWriteQueue::queueTrkInitialPing() +{ + // Ping, reset sequence count + m_trkWriteToken = 0; + m_trkWriteQueue.append(TrkMessage(TrkPing, 0)); +} + +/////////////////////////////////////////////////////////////////////// +// +// DeviceContext to be shared between threads +// +/////////////////////////////////////////////////////////////////////// + +struct DeviceContext { + DeviceContext(); +#ifdef Q_OS_WIN + HANDLE device; + OVERLAPPED readOverlapped; + OVERLAPPED writeOverlapped; +#else + QFile file; +#endif + bool serialFrame; + QMutex mutex; +}; + +DeviceContext::DeviceContext() : +#ifdef Q_OS_WIN + device(INVALID_HANDLE_VALUE), +#endif + serialFrame(true) +{ +} + +/////////////////////////////////////////////////////////////////////// +// +// TrkWriterThread: A thread operating a TrkWriteQueue. +// with exception of the handling of the TRK_WRITE_QUEUE_NOOP_CODE +// synchronization message. The invocation of the callback is then +// done by the thread owning the TrkWriteQueue, while pendingMessage() is called +// from another thread. This happens via a Qt::BlockingQueuedConnection. + +/////////////////////////////////////////////////////////////////////// + +class WriterThread : public QThread +{ + Q_OBJECT + Q_DISABLE_COPY(WriterThread) +public: + explicit WriterThread(const QSharedPointer<DeviceContext> &context); + + // Enqueue messages. + void queueTrkMessage(byte code, TrkCallback callback, + const QByteArray &data, const QVariant &cookie); + void queueTrkInitialPing(); + + void clearWriteQueue(); + + // Call this from the device read notification with the results. + void slotHandleResult(const TrkResult &result); + + virtual void run(); + +signals: + void error(const QString &); + void internalNoopMessageDequeued(const trk::TrkMessage&); + +public slots: + bool trkWriteRawMessage(const TrkMessage &msg); + void terminate(); + void tryWrite(); + +private slots: + void invokeNoopMessage(const trk::TrkMessage &); + +private: + bool write(const QByteArray &data, QString *errorMessage); + inline int writePendingMessage(); + + const QSharedPointer<DeviceContext> m_context; + QMutex m_dataMutex; + QMutex m_waitMutex; + QWaitCondition m_waitCondition; + TrkWriteQueue m_queue; + bool m_terminate; +}; + +WriterThread::WriterThread(const QSharedPointer<DeviceContext> &context) : + m_context(context), + m_terminate(false) +{ + static const int trkMessageMetaId = qRegisterMetaType<trk::TrkMessage>(); + Q_UNUSED(trkMessageMetaId) + connect(this, SIGNAL(internalNoopMessageDequeued(trk::TrkMessage)), + this, SLOT(invokeNoopMessage(trk::TrkMessage)), Qt::BlockingQueuedConnection); +} + +void WriterThread::run() +{ + while (writePendingMessage() == 0) ; +} + +int WriterThread::writePendingMessage() +{ + enum { MaxAttempts = 100, RetryIntervalMS = 200 }; + + // Wait. Use a timeout in case something is already queued before we + // start up or some weird hanging exit condition + m_waitMutex.lock(); + m_waitCondition.wait(&m_waitMutex, 100); + m_waitMutex.unlock(); + if (m_terminate) + return 1; + + // Send off message + m_dataMutex.lock(); + TrkMessage message; + const TrkWriteQueue::PendingMessageResult pr = m_queue.pendingMessage(&message); + m_dataMutex.unlock(); + + switch (pr) { + case TrkWriteQueue::NoMessage: + break; + case TrkWriteQueue::PendingMessage: { + //qDebug() << "Write pending message " << message; + // Untested: try to re-send a few times + bool success = false; + for (int r = 0; !success && (r < MaxAttempts); r++) { + success = trkWriteRawMessage(message); + if (!success) { + emit error(QString::fromLatin1("Write failure, attempt %1 of %2.").arg(r).arg(int(MaxAttempts))); + if (m_terminate) + return 1; + QThread::msleep(RetryIntervalMS); + } + } + // Notify queue. If still failed, give up. + m_dataMutex.lock(); + m_queue.notifyWriteResult(success ? TrkWriteQueue::WriteOk : TrkWriteQueue::WriteFailedDiscard); + m_dataMutex.unlock(); + } + break; + case TrkWriteQueue::NoopMessageDequeued: + // Sync with thread that owns us via a blocking signal + if (verboseTrk) + qDebug() << "Noop message dequeued" << message; + emit internalNoopMessageDequeued(message); + break; + } // switch + return 0; +} + +void WriterThread::invokeNoopMessage(const trk::TrkMessage &msg) +{ + TrkWriteQueue::invokeNoopMessage(msg); +} + +void WriterThread::terminate() +{ + m_terminate = true; + m_waitCondition.wakeAll(); + wait(); + m_terminate = false; + m_queue.clear(); +} + +#ifdef Q_OS_WIN + +static inline QString msgTerminated(int size) +{ + return QString::fromLatin1("Terminated with %1 bytes pending.").arg(size); +} + +// Interruptible synchronous write function. +static inline bool overlappedSyncWrite(HANDLE file, + const bool &terminateFlag, + const char *data, + DWORD size, DWORD *charsWritten, + OVERLAPPED *overlapped, + QString *errorMessage) +{ + if (WriteFile(file, data, size, charsWritten, overlapped)) + return true; + const DWORD writeError = GetLastError(); + if (writeError != ERROR_IO_PENDING) { + *errorMessage = QString::fromLatin1("WriteFile failed: %1").arg(winErrorMessage(writeError)); + return false; + } + // Wait for written or thread terminated + const DWORD timeoutMS = 200; + const unsigned maxAttempts = 20; + DWORD wr = WaitForSingleObject(overlapped->hEvent, timeoutMS); + for (unsigned n = 0; wr == WAIT_TIMEOUT && n < maxAttempts && !terminateFlag; + wr = WaitForSingleObject(overlapped->hEvent, timeoutMS), n++); + if (terminateFlag) { + *errorMessage = msgTerminated(size); + return false; + } + switch (wr) { + case WAIT_OBJECT_0: + break; + case WAIT_TIMEOUT: + *errorMessage = QString::fromLatin1("Write timed out."); + return false; + default: + *errorMessage = QString::fromLatin1("Error while waiting for WriteFile results: %1").arg(winErrorMessage(GetLastError())); + return false; + } + if (!GetOverlappedResult(file, overlapped, charsWritten, TRUE)) { + *errorMessage = QString::fromLatin1("Error writing %1 bytes: %2").arg(size).arg(winErrorMessage(GetLastError())); + return false; + } + return true; +} +#endif + +bool WriterThread::write(const QByteArray &data, QString *errorMessage) +{ + if (verboseTrk) + qDebug() << "Write raw data: " << stringFromArray(data).toLatin1(); + QMutexLocker locker(&m_context->mutex); +#ifdef Q_OS_WIN + DWORD charsWritten; + if (!overlappedSyncWrite(m_context->device, m_terminate, data.data(), data.size(), &charsWritten, &m_context->writeOverlapped, errorMessage)) { + return false; + } + FlushFileBuffers(m_context->device); + return true; +#else + if (m_context->file.write(data) == -1 || !m_context->file.flush()) { + *errorMessage = QString::fromLatin1("Cannot write: %1").arg(m_context->file.errorString()); + return false; + } + return true; +#endif +} + +bool WriterThread::trkWriteRawMessage(const TrkMessage &msg) +{ + const QByteArray ba = frameMessage(msg.code, msg.token, msg.data, m_context->serialFrame); + QString errorMessage; + const bool rc = write(ba, &errorMessage); + if (!rc) { + qWarning("%s\n", qPrintable(errorMessage)); + emit error(errorMessage); + } + return rc; +} + +void WriterThread::tryWrite() +{ + m_waitCondition.wakeAll(); +} + +void WriterThread::queueTrkMessage(byte code, TrkCallback callback, + const QByteArray &data, const QVariant &cookie) +{ + m_dataMutex.lock(); + m_queue.queueTrkMessage(code, callback, data, cookie); + m_dataMutex.unlock(); + tryWrite(); +} + +void WriterThread::clearWriteQueue() +{ + m_dataMutex.lock(); + m_queue.clear(); + m_dataMutex.unlock(); +} + +void WriterThread::queueTrkInitialPing() +{ + m_dataMutex.lock(); + m_queue.queueTrkInitialPing(); + m_dataMutex.unlock(); + tryWrite(); +} + +// Call this from the device read notification with the results. +void WriterThread::slotHandleResult(const TrkResult &result) +{ + m_queue.slotHandleResult(result, &m_dataMutex); + tryWrite(); // Have messages been enqueued in-between? +} + + +/////////////////////////////////////////////////////////////////////// +// +// ReaderThreadBase: Base class for a thread that reads data from +// the device, decodes the messages and emit signals for the messages. +// A Qt::BlockingQueuedConnection should be used for the message signal +// to ensure messages are processed in the correct sequence. +// +/////////////////////////////////////////////////////////////////////// + +class ReaderThreadBase : public QThread +{ + Q_OBJECT + Q_DISABLE_COPY(ReaderThreadBase) +public: + + int bytesPending() const { return m_trkReadBuffer.size(); } + +signals: + void messageReceived(const trk::TrkResult &result, const QByteArray &rawData); + +protected: + explicit ReaderThreadBase(const QSharedPointer<DeviceContext> &context); + void processData(const QByteArray &a); + void processData(char c); + + const QSharedPointer<DeviceContext> m_context; + +private: + void readMessages(); + + QByteArray m_trkReadBuffer; + bool linkEstablishmentMode; +}; + +ReaderThreadBase::ReaderThreadBase(const QSharedPointer<DeviceContext> &context) : + m_context(context), linkEstablishmentMode(true) +{ + static const int trkResultMetaId = qRegisterMetaType<trk::TrkResult>(); + Q_UNUSED(trkResultMetaId) +} + +void ReaderThreadBase::processData(const QByteArray &a) +{ + m_trkReadBuffer += a; + readMessages(); +} + +void ReaderThreadBase::processData(char c) +{ + m_trkReadBuffer += c; + if (m_trkReadBuffer.size() > 1) + readMessages(); +} + +void ReaderThreadBase::readMessages() +{ + TrkResult r; + QByteArray rawData; + while (extractResult(&m_trkReadBuffer, m_context->serialFrame, &r, linkEstablishmentMode, &rawData)) { + emit messageReceived(r, rawData); + } +} + +#ifdef Q_OS_WIN +/////////////////////////////////////////////////////////////////////// +// +// WinReaderThread: A thread reading from the device using Windows API. +// Waits on an overlapped I/O handle and an event that tells the thread to +// terminate. +// +/////////////////////////////////////////////////////////////////////// + +class WinReaderThread : public ReaderThreadBase +{ + Q_OBJECT + Q_DISABLE_COPY(WinReaderThread) +public: + explicit WinReaderThread(const QSharedPointer<DeviceContext> &context); + ~WinReaderThread(); + + virtual void run(); + +signals: + void error(const QString &); + +public slots: + void terminate(); + +private: + enum Handles { FileHandle, TerminateEventHandle, HandleCount }; + + inline int tryRead(); + + HANDLE m_handles[HandleCount]; +}; + +WinReaderThread::WinReaderThread(const QSharedPointer<DeviceContext> &context) : + ReaderThreadBase(context) +{ + m_handles[FileHandle] = NULL; + m_handles[TerminateEventHandle] = CreateEvent(NULL, FALSE, FALSE, NULL); +} + +WinReaderThread::~WinReaderThread() +{ + CloseHandle(m_handles[TerminateEventHandle]); +} + +// Return 0 to continue or error code +int WinReaderThread::tryRead() +{ + enum { BufSize = 1024 }; + char buffer[BufSize]; + // Check if there are already bytes waiting. If not, wait for first byte + COMSTAT comStat; + if (!ClearCommError(m_context->device, NULL, &comStat)){ + emit error(QString::fromLatin1("ClearCommError failed: %1").arg(winErrorMessage(GetLastError()))); + return -7; + } + const DWORD bytesToRead = qMax(DWORD(1), qMin(comStat.cbInQue, DWORD(BufSize))); + // Trigger read + DWORD bytesRead = 0; + if (ReadFile(m_context->device, &buffer, bytesToRead, &bytesRead, &m_context->readOverlapped)) { + if (bytesRead == 1) { + processData(buffer[0]); + } else { + processData(QByteArray(buffer, bytesRead)); + } + return 0; + } + const DWORD readError = GetLastError(); + if (readError != ERROR_IO_PENDING) { + emit error(QString::fromLatin1("Read error: %1").arg(winErrorMessage(readError))); + return -1; + } + // Wait for either termination or data + const DWORD wr = WaitForMultipleObjects(HandleCount, m_handles, false, INFINITE); + if (wr == WAIT_FAILED) { + emit error(QString::fromLatin1("Wait failed: %1").arg(winErrorMessage(GetLastError()))); + return -2; + } + if (wr - WAIT_OBJECT_0 == TerminateEventHandle) { + return 1; // Terminate + } + // Check data + if (!GetOverlappedResult(m_context->device, &m_context->readOverlapped, &bytesRead, true)) { + emit error(QString::fromLatin1("GetOverlappedResult failed: %1").arg(winErrorMessage(GetLastError()))); + return -3; + } + if (bytesRead == 1) { + processData(buffer[0]); + } else { + processData(QByteArray(buffer, bytesRead)); + } + return 0; +} + +void WinReaderThread::run() +{ + m_handles[FileHandle] = m_context->readOverlapped.hEvent; + while ( tryRead() == 0) ; +} + +void WinReaderThread::terminate() +{ + SetEvent(m_handles[TerminateEventHandle]); + wait(); +} + +typedef WinReaderThread ReaderThread; + +#else + +/////////////////////////////////////////////////////////////////////// +// +// UnixReaderThread: A thread reading from the device. +// Uses select() to wait and a special ioctl() to find out the number +// of bytes queued. For clean termination, the self-pipe trick is used. +// The class maintains a pipe, on whose read end the select waits besides +// the device file handle. To terminate, a byte is written to the pipe. +// +/////////////////////////////////////////////////////////////////////// + +static inline QString msgUnixCallFailedErrno(const char *func, int errorNumber) +{ + return QString::fromLatin1("Call to %1() failed: %2").arg(QLatin1String(func), QString::fromLocal8Bit(strerror(errorNumber))); +} + +class UnixReaderThread : public ReaderThreadBase { + Q_OBJECT + Q_DISABLE_COPY(UnixReaderThread) +public: + explicit UnixReaderThread(const QSharedPointer<DeviceContext> &context); + ~UnixReaderThread(); + + virtual void run(); + +signals: + void error(const QString &); + +public slots: + void terminate(); + +private: + inline int tryRead(); + + int m_terminatePipeFileDescriptors[2]; +}; + +UnixReaderThread::UnixReaderThread(const QSharedPointer<DeviceContext> &context) : + ReaderThreadBase(context) +{ + m_terminatePipeFileDescriptors[0] = m_terminatePipeFileDescriptors[1] = -1; + // Set up pipes for termination. Should not fail + if (pipe(m_terminatePipeFileDescriptors) < 0) + qWarning("%s\n", qPrintable(msgUnixCallFailedErrno("pipe", errno))); +} + +UnixReaderThread::~UnixReaderThread() +{ + close(m_terminatePipeFileDescriptors[0]); + close(m_terminatePipeFileDescriptors[1]); +} + +int UnixReaderThread::tryRead() +{ + fd_set readSet, tempReadSet, tempExceptionSet; + struct timeval timeOut; + const int fileDescriptor = m_context->file.handle(); + FD_ZERO(&readSet); + FD_SET(fileDescriptor, &readSet); + FD_SET(m_terminatePipeFileDescriptors[0], &readSet); + const int maxFileDescriptor = qMax(m_terminatePipeFileDescriptors[0], fileDescriptor); + int result = 0; + do { + memcpy(&tempReadSet, &readSet, sizeof(fd_set)); + memcpy(&tempExceptionSet, &readSet, sizeof(fd_set)); + timeOut.tv_sec = 1; + timeOut.tv_usec = 0; + result = select(maxFileDescriptor + 1, &tempReadSet, NULL, &tempExceptionSet, &timeOut); + } while ( result < 0 && errno == EINTR ); + // Timeout? + if (result == 0) + return 0; + // Something wrong? + if (result < 0) { + emit error(msgUnixCallFailedErrno("select", errno)); + return -1; + } + // Did the exception set trigger on the device? + if (FD_ISSET(fileDescriptor,&tempExceptionSet)) { + emit error(QLatin1String("An Exception occurred on the device.")); + return -2; + } + // Check termination pipe. + if (FD_ISSET(m_terminatePipeFileDescriptors[0], &tempReadSet) + || FD_ISSET(m_terminatePipeFileDescriptors[0], &tempExceptionSet)) + return 1; + + // determine number of pending bytes and read + int numBytes; + if (ioctl(fileDescriptor, FIONREAD, &numBytes) < 0) { + emit error(msgUnixCallFailedErrno("ioctl", errno)); + return -1; + } + m_context->mutex.lock(); + const QByteArray data = m_context->file.read(numBytes); + m_context->mutex.unlock(); + processData(data); + return 0; +} + +void UnixReaderThread::run() +{ + // Read loop + while (tryRead() == 0) + ; +} + +void UnixReaderThread::terminate() +{ + // Trigger select() by writing to the pipe + char c = 0; + const int written = write(m_terminatePipeFileDescriptors[1], &c, 1); + Q_UNUSED(written) + wait(); +} + +typedef UnixReaderThread ReaderThread; + +#endif + +/////////////////////////////////////////////////////////////////////// +// +// TrkDevicePrivate +// +/////////////////////////////////////////////////////////////////////// + +struct TrkDevicePrivate +{ + TrkDevicePrivate(); + + QSharedPointer<DeviceContext> deviceContext; + QScopedPointer<WriterThread> writerThread; + QScopedPointer<ReaderThread> readerThread; + + QByteArray trkReadBuffer; + int verbose; + QString errorString; + QString port; +}; + +/////////////////////////////////////////////////////////////////////// +// +// TrkDevice +// +/////////////////////////////////////////////////////////////////////// + +TrkDevicePrivate::TrkDevicePrivate() : + deviceContext(new DeviceContext), + verbose(0) +{ +} + +/////////////////////////////////////////////////////////////////////// +// +// TrkDevice +// +/////////////////////////////////////////////////////////////////////// + +TrkDevice::TrkDevice(QObject *parent) : + QObject(parent), + d(new TrkDevicePrivate) +{} + +TrkDevice::~TrkDevice() +{ + close(); + delete d; +} + +bool TrkDevice::open(QString *errorMessage) +{ + if (d->verbose || verboseTrk) + qDebug() << "Opening" << port() << "is open: " << isOpen() << " serialFrame=" << serialFrame(); + if (isOpen()) + return true; + if (d->port.isEmpty()) { + *errorMessage = QLatin1String("Internal error: No port set on TrkDevice"); + return false; + } +#ifdef Q_OS_WIN + const QString fullPort = QLatin1String("\\\\.\\") + d->port; + d->deviceContext->device = CreateFile(reinterpret_cast<const WCHAR*>(fullPort.utf16()), + GENERIC_READ | GENERIC_WRITE, + 0, + NULL, + OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL|FILE_FLAG_NO_BUFFERING|FILE_FLAG_OVERLAPPED, + NULL); + + if (INVALID_HANDLE_VALUE == d->deviceContext->device) { + *errorMessage = QString::fromLatin1("Could not open device '%1': %2").arg(port(), winErrorMessage(GetLastError())); + return false; + } + memset(&d->deviceContext->readOverlapped, 0, sizeof(OVERLAPPED)); + d->deviceContext->readOverlapped.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); + memset(&d->deviceContext->writeOverlapped, 0, sizeof(OVERLAPPED)); + d->deviceContext->writeOverlapped.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); + if (d->deviceContext->readOverlapped.hEvent == NULL || d->deviceContext->writeOverlapped.hEvent == NULL) { + *errorMessage = QString::fromLatin1("Failed to create events: %1").arg(winErrorMessage(GetLastError())); + return false; + } +#else + d->deviceContext->file.setFileName(d->port); + if (!d->deviceContext->file.open(QIODevice::ReadWrite|QIODevice::Unbuffered)) { + *errorMessage = QString::fromLatin1("Cannot open %1: %2").arg(d->port, d->deviceContext->file.errorString()); + return false; + } + + struct termios termInfo; + if (tcgetattr(d->deviceContext->file.handle(), &termInfo) < 0) { + *errorMessage = QString::fromLatin1("Unable to retrieve terminal settings: %1 %2").arg(errno).arg(QString::fromAscii(strerror(errno))); + return false; + } + // Turn off terminal echo as not get messages back, among other things + termInfo.c_cflag |= CREAD|CLOCAL; + termInfo.c_lflag &= (~(ICANON|ECHO|ECHOE|ECHOK|ECHONL|ISIG)); + termInfo.c_iflag &= (~(INPCK|IGNPAR|PARMRK|ISTRIP|ICRNL|IXANY)); + 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->deviceContext->file.handle(), TCSAFLUSH, &termInfo) < 0) { + *errorMessage = QString::fromLatin1("Unable to apply terminal settings: %1 %2").arg(errno).arg(QString::fromAscii(strerror(errno))); + return false; + } +#endif + d->readerThread.reset(new ReaderThread(d->deviceContext)); + connect(d->readerThread.data(), SIGNAL(error(QString)), this, SLOT(emitError(QString)), + Qt::QueuedConnection); + connect(d->readerThread.data(), SIGNAL(messageReceived(trk::TrkResult,QByteArray)), + this, SLOT(slotMessageReceived(trk::TrkResult,QByteArray)), + Qt::QueuedConnection); + d->readerThread->start(); + + d->writerThread.reset(new WriterThread(d->deviceContext)); + connect(d->writerThread.data(), SIGNAL(error(QString)), this, SLOT(emitError(QString)), + Qt::QueuedConnection); + d->writerThread->start(); + + if (d->verbose || verboseTrk) + qDebug() << "Opened" << d->port << d->readerThread.data() << d->writerThread.data(); + return true; +} + +void TrkDevice::close() +{ + if (verboseTrk) + qDebug() << "close" << d->port << " is open: " << isOpen() + << " read pending " << (d->readerThread.isNull() ? 0 : d->readerThread->bytesPending()) + << sender(); + if (!isOpen()) + return; + if (d->readerThread) + d->readerThread->terminate(); + if (d->writerThread) + d->writerThread->terminate(); +#ifdef Q_OS_WIN + CloseHandle(d->deviceContext->device); + d->deviceContext->device = INVALID_HANDLE_VALUE; + CloseHandle(d->deviceContext->readOverlapped.hEvent); + CloseHandle(d->deviceContext->writeOverlapped.hEvent); + d->deviceContext->readOverlapped.hEvent = d->deviceContext->writeOverlapped.hEvent = NULL; +#else + d->deviceContext->file.close(); +#endif + + if (d->verbose) + emitLogMessage("Close"); +} + +bool TrkDevice::isOpen() const +{ +#ifdef Q_OS_WIN + return d->deviceContext->device != INVALID_HANDLE_VALUE; +#else + return d->deviceContext->file.isOpen(); +#endif +} + +QString TrkDevice::port() const +{ + return d->port; +} + +void TrkDevice::setPort(const QString &p) +{ + if (verboseTrk) + qDebug() << "setPort" << p; + d->port = p; +} + +QString TrkDevice::errorString() const +{ + return d->errorString; +} + +bool TrkDevice::serialFrame() const +{ + return d->deviceContext->serialFrame; +} + +void TrkDevice::setSerialFrame(bool f) +{ + if (verboseTrk) + qDebug() << "setSerialFrame" << f; + d->deviceContext->serialFrame = f; +} + +int TrkDevice::verbose() const +{ + return d->verbose; +} + +void TrkDevice::setVerbose(int b) +{ + d->verbose = b; +} + +void TrkDevice::slotMessageReceived(const trk::TrkResult &result, const QByteArray &rawData) +{ + if (isOpen()) { // Might receive bytes after closing due to queued connections. + d->writerThread->slotHandleResult(result); + if (d->verbose > 1) + qDebug() << "Received: " << result.toString(); + emit messageReceived(result); + if (!rawData.isEmpty()) + emit rawDataReceived(rawData); + } +} + +void TrkDevice::emitError(const QString &s) +{ + d->errorString = s; + qWarning("%s\n", qPrintable(s)); + emit error(s); +} + +void TrkDevice::clearWriteQueue() +{ + if (isOpen()) + d->writerThread->clearWriteQueue(); +} + +void TrkDevice::sendTrkMessage(byte code, TrkCallback callback, + const QByteArray &data, const QVariant &cookie) +{ + if (!isOpen()) { + emitError(msgAccessingClosedDevice(d->port)); + return; + } + if (!d->writerThread.isNull()) { + if (d->verbose > 1) { + QByteArray msg = "Sending: 0x"; + msg += QByteArray::number(code, 16); + msg += ": "; + msg += stringFromArray(data).toLatin1(); + if (cookie.isValid()) + msg += " Cookie: " + cookie.toString().toLatin1(); + qDebug("%s", msg.data()); + } + d->writerThread->queueTrkMessage(code, callback, data, cookie); + } +} + +void TrkDevice::sendTrkInitialPing() +{ + if (!isOpen()) { + emitError(msgAccessingClosedDevice(d->port)); + return; + } + if (!d->writerThread.isNull()) + d->writerThread->queueTrkInitialPing(); +} + +bool TrkDevice::sendTrkAck(byte token) +{ + if (!isOpen()) { + emitError(msgAccessingClosedDevice(d->port)); + return false; + } + if (d->writerThread.isNull()) + return false; + // The acknowledgement must not be queued! + TrkMessage msg(0x80, token); + msg.token = token; + msg.data.append('\0'); + if (verboseTrk) + qDebug() << "Write synchroneous message: " << msg; + return d->writerThread->trkWriteRawMessage(msg); + // 01 90 00 07 7e 80 01 00 7d 5e 7e +} + +void TrkDevice::emitLogMessage(const QString &msg) +{ + if (d->verbose) + qDebug("%s\n", qPrintable(msg)); + emit logMessage(msg); +} + +} // namespace trk + +#include "trkdevice.moc" diff --git a/src/runonphone/symbianutils/trkdevice.h b/src/runonphone/symbianutils/trkdevice.h new file mode 100644 index 000000000..bb54be520 --- /dev/null +++ b/src/runonphone/symbianutils/trkdevice.h @@ -0,0 +1,143 @@ +/**************************************************************************** +** +** 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 TRKDEVICE_H +#define TRKDEVICE_H + +#include "symbianutils_global.h" +#include "callback.h" + +#include <QtCore/QObject> +#include <QtCore/QVariant> +#include <QtCore/QByteArray> +#include <QtCore/QSharedPointer> + +QT_BEGIN_NAMESPACE +class QIODevice; +QT_END_NAMESPACE + +namespace trk { + +struct TrkResult; +struct TrkMessage; +struct TrkDevicePrivate; + +/* TrkDevice: Implements a Windows COM or Linux device for + * Trk communications. Provides synchronous write and asynchronous + * read operation. + * The serialFrames property specifies whether packets are encapsulated in + * "0x90 <length>" frames, which is currently the case for serial ports. + * Contains a write message queue allowing + * for queueing messages with a notification callback. If the message receives + * an ACK, the callback is invoked. + * The special message TRK_WRITE_QUEUE_NOOP_CODE code can be used for synchronization. + * The respective message will not be sent, the callback is just invoked. + * Note that calling open/close in quick succession can cause crashes + * due to the use of queused signals. */ + +enum { TRK_WRITE_QUEUE_NOOP_CODE = 0x7f }; + +typedef trk::Callback<const TrkResult &> TrkCallback; + +class SYMBIANUTILS_EXPORT TrkDevice : public QObject +{ + Q_OBJECT + Q_PROPERTY(bool serialFrame READ serialFrame WRITE setSerialFrame) + Q_PROPERTY(bool verbose READ verbose WRITE setVerbose) + Q_PROPERTY(QString port READ port WRITE setPort) +public: + explicit TrkDevice(QObject *parent = 0); + virtual ~TrkDevice(); + + bool open(QString *errorMessage); + bool isOpen() const; + + QString port() const; + void setPort(const QString &p); + + QString errorString() const; + + bool serialFrame() const; + void setSerialFrame(bool f); + + int verbose() const; + void setVerbose(int b); + + // Enqueue a message with a notification callback. + void sendTrkMessage(unsigned char code, + TrkCallback callBack = TrkCallback(), + const QByteArray &data = QByteArray(), + const QVariant &cookie = QVariant()); + + // Enqeue an initial ping + void sendTrkInitialPing(); + + // Send an Ack synchronously, bypassing the queue + bool sendTrkAck(unsigned char token); + +public slots: + void clearWriteQueue(); + +signals: + void messageReceived(const trk::TrkResult &result); + // Emitted with the contents of messages enclosed in 07e, not for log output + void rawDataReceived(const QByteArray &data); + void error(const QString &msg); + void logMessage(const QString &msg); + +private slots: + void slotMessageReceived(const trk::TrkResult &result, const QByteArray &a); + +protected slots: + void emitError(const QString &msg); + void emitLogMessage(const QString &msg); + +public slots: + void close(); + +private: + void readMessages(); + TrkDevicePrivate *d; +}; + +} // namespace trk + +#endif // TRKDEVICE_H diff --git a/src/runonphone/symbianutils/trkutils.cpp b/src/runonphone/symbianutils/trkutils.cpp new file mode 100644 index 000000000..168f53960 --- /dev/null +++ b/src/runonphone/symbianutils/trkutils.cpp @@ -0,0 +1,603 @@ +/**************************************************************************** +** +** 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 "trkutils.h" +#include <ctype.h> + +#include <QtCore/QCoreApplication> +#include <QtCore/QDebug> +#include <QtCore/QDate> +#include <QtCore/QDateTime> +#include <QtCore/QTime> + +#define logMessage(s) do { qDebug() << "TRKCLIENT: " << s; } while (0) + +namespace trk { + +Library::Library() : codeseg(0), dataseg(0), pid(0) +{ +} + +Library::Library(const TrkResult &result) : codeseg(0), dataseg(0), pid(0) +{ + if (result.data.size() < 20) { + qWarning("Invalid trk creation notification received."); + return; + } + + const char *data = result.data.constData(); + pid = extractInt(data + 2); + codeseg = extractInt(data + 10); + dataseg = extractInt(data + 14); + const uint len = extractShort(data + 18); + name = result.data.mid(20, len); +} + +TrkAppVersion::TrkAppVersion() +{ + reset(); +} + +void TrkAppVersion::reset() +{ + trkMajor = trkMinor= protocolMajor = protocolMinor = 0; +} + +Session::Session() +{ + reset(); +} + +void Session::reset() +{ + cpuMajor = 0; + cpuMinor = 0; + bigEndian = 0; + defaultTypeSize = 0; + fpTypeSize = 0; + extended1TypeSize = 0; + extended2TypeSize = 0; + pid = 0; + mainTid = 0; + tid = 0; + codeseg = 0; + dataseg = 0; + + libraries.clear(); + trkAppVersion.reset(); +} + +static QString formatCpu(int major, int minor) +{ + //: CPU description of an S60 device + //: %1 major verison, %2 minor version + //: %3 real name of major verison, %4 real name of minor version + const QString str = QCoreApplication::translate("trk::Session", "CPU: v%1.%2%3%4"); + QString majorStr; + QString minorStr; + switch (major) { + case 0x04: + majorStr = " ARM"; + break; + } + switch (minor) { + case 0x00: + minorStr = " 920T"; + break; + } + return str.arg(major).arg(minor).arg(majorStr).arg(minorStr); + } + +QString formatTrkVersion(const TrkAppVersion &version) +{ + QString str = QCoreApplication::translate("trk::Session", + "App TRK: v%1.%2 TRK protocol: v%3.%4"); + str = str.arg(version.trkMajor).arg(version.trkMinor); + return str.arg(version.protocolMajor).arg(version.protocolMinor); +} + +QString Session::deviceDescription(unsigned verbose) const +{ + if (!cpuMajor) + return QString(); + + //: s60description + //: description of an S60 device + //: %1 CPU description, %2 endianness + //: %3 default type size (if any), %4 float size (if any) + //: %5 TRK version + QString msg = QCoreApplication::translate("trk::Session", "%1, %2%3%4, %5"); + QString endianness = bigEndian + ? QCoreApplication::translate("trk::Session", "big endian") + : QCoreApplication::translate("trk::Session", "little endian"); + msg = msg.arg(formatCpu(cpuMajor, cpuMinor)).arg(endianness); + //: The separator in a list of strings + QString defaultTypeSizeStr; + QString fpTypeSizeStr; + if (verbose && defaultTypeSize) + //: will be inserted into s60description + defaultTypeSizeStr = QCoreApplication::translate("trk::Session", ", type size: %1").arg(defaultTypeSize); + if (verbose && fpTypeSize) + //: will be inserted into s60description + fpTypeSizeStr = QCoreApplication::translate("trk::Session", ", float size: %1").arg(fpTypeSize); + msg = msg.arg(defaultTypeSizeStr).arg(fpTypeSizeStr); + return msg.arg(formatTrkVersion(trkAppVersion)); +} + +QByteArray Session::gdbLibraryList() const +{ + const int count = libraries.size(); + QByteArray response = "l<library-list>"; + for (int i = 0; i != count; ++i) { + const trk::Library &lib = libraries.at(i); + response += "<library name=\""; + response += lib.name; + response += "\">"; + response += "<section address=\"0x"; + response += trk::hexNumber(lib.codeseg); + response += "\"/>"; + response += "<section address=\"0x"; + response += trk::hexNumber(lib.dataseg); + response += "\"/>"; + response += "<section address=\"0x"; + response += trk::hexNumber(lib.dataseg); + response += "\"/>"; + response += "</library>"; + } + response += "</library-list>"; + return response; +} + +QByteArray Session::gdbQsDllInfo(int start, int count) const +{ + // Happens with gdb 6.4.50.20060226-cvs / CodeSourcery. + // Never made it into FSF gdb that got qXfer:libraries:read instead. + // http://sourceware.org/ml/gdb/2007-05/msg00038.html + // Name=hexname,TextSeg=textaddr[,DataSeg=dataaddr] + const int libraryCount = libraries.size(); + const int end = count < 0 ? libraryCount : qMin(libraryCount, start + count); + QByteArray response(1, end == libraryCount ? 'l' : 'm'); + for (int i = start; i < end; ++i) { + if (i != start) + response += ';'; + const Library &lib = libraries.at(i); + response += "Name="; + response += lib.name.toHex(); + response += ",TextSeg="; + response += hexNumber(lib.codeseg); + response += ",DataSeg="; + response += hexNumber(lib.dataseg); + } + return response; +} + +QString Session::toString() const +{ + QString rc; + QTextStream str(&rc); + str << "Session: " << deviceDescription(false) << '\n' + << "pid: " << pid << "main thread: " << mainTid + << " current thread: " << tid << ' '; + str.setIntegerBase(16); + str << " code: 0x" << codeseg << " data: 0x" << dataseg << '\n'; + if (const int libCount = libraries.size()) { + str << "Libraries:\n"; + for (int i = 0; i < libCount; i++) + str << " #" << i << ' ' << libraries.at(i).name + << " code: 0x" << libraries.at(i).codeseg + << " data: 0x" << libraries.at(i).dataseg << '\n'; + } + if (const int moduleCount = modules.size()) { + str << "Modules:\n"; + for (int i = 0; i < moduleCount; i++) + str << " #" << i << ' ' << modules.at(i) << '\n'; + } + str.setIntegerBase(10); + if (!addressToBP.isEmpty()) { + typedef QHash<uint, uint>::const_iterator BP_ConstIterator; + str << "Breakpoints:\n"; + const BP_ConstIterator cend = addressToBP.constEnd(); + for (BP_ConstIterator it = addressToBP.constBegin(); it != cend; ++it) { + str.setIntegerBase(16); + str << " 0x" << it.key(); + str.setIntegerBase(10); + str << ' ' << it.value() << '\n'; + } + } + + return rc; +} + +// -------------- + +QByteArray decode7d(const QByteArray &ba) +{ + QByteArray res; + res.reserve(ba.size()); + for (int i = 0; i < ba.size(); ++i) { + byte c = byte(ba.at(i)); + if (c == 0x7d) { + ++i; + c = 0x20 ^ byte(ba.at(i)); + } + res.append(c); + } + return res; +} + +QByteArray encode7d(const QByteArray &ba) +{ + QByteArray res; + res.reserve(ba.size() + 2); + for (int i = 0; i < ba.size(); ++i) { + byte c = byte(ba.at(i)); + if (c == 0x7e || c == 0x7d) { + res.append(0x7d); + res.append(0x20 ^ c); + } else { + res.append(c); + } + } + return res; +} + +// FIXME: Use the QByteArray based version below? +static inline QString stringFromByte(byte c) +{ + return QString::fromLatin1("%1").arg(c, 2, 16, QChar('0')); +} + +SYMBIANUTILS_EXPORT QString stringFromArray(const QByteArray &ba, int maxLen) +{ + QString str; + QString ascii; + const int size = maxLen == -1 ? ba.size() : qMin(ba.size(), maxLen); + for (int i = 0; i < size; ++i) { + const int c = byte(ba.at(i)); + str += QString::fromAscii("%1 ").arg(c, 2, 16, QChar('0')); + ascii += QChar(c).isPrint() ? QChar(c) : QChar('.'); + } + if (size != ba.size()) { + str += QLatin1String("..."); + ascii += QLatin1String("..."); + } + return str + QLatin1String(" ") + ascii; +} + +SYMBIANUTILS_EXPORT QByteArray hexNumber(uint n, int digits) +{ + QByteArray ba = QByteArray::number(n, 16); + if (digits == 0 || ba.size() == digits) + return ba; + return QByteArray(digits - ba.size(), '0') + ba; +} + +SYMBIANUTILS_EXPORT QByteArray hexxNumber(uint n, int digits) +{ + return "0x" + hexNumber(n, digits); +} + +TrkResult::TrkResult() : + code(0), + token(0), + isDebugOutput(false) +{ +} + +void TrkResult::clear() +{ + code = token= 0; + isDebugOutput = false; + data.clear(); + cookie = QVariant(); +} + +QString TrkResult::toString() const +{ + QString res = stringFromByte(code); + res += QLatin1String(" ["); + res += stringFromByte(token); + res += QLatin1Char(']'); + res += QLatin1Char(' '); + res += stringFromArray(data); + return res; +} + +QByteArray frameMessage(byte command, byte token, const QByteArray &data, bool serialFrame) +{ + byte s = command + token; + for (int i = 0; i != data.size(); ++i) + s += data.at(i); + byte checksum = 255 - (s & 0xff); + //int x = s + ~s; + //logMessage("check: " << s << checksum << x; + + QByteArray response; + response.reserve(data.size() + 3); + response.append(char(command)); + response.append(char(token)); + response.append(data); + response.append(char(checksum)); + + QByteArray encodedData = encode7d(response); + + QByteArray ba; + ba.reserve(encodedData.size() + 6); + if (serialFrame) { + ba.append(char(0x01)); + ba.append(char(0x90)); + const ushort encodedSize = encodedData.size() + 2; // 2 x 0x7e + appendShort(&ba, encodedSize, BigEndian); + } + ba.append(char(0x7e)); + ba.append(encodedData); + ba.append(char(0x7e)); + + return ba; +} + +/* returns 0 if array doesn't represent a result, +otherwise returns the length of the result data */ +ushort isValidTrkResult(const QByteArray &buffer, bool serialFrame, ushort& mux) +{ + if (serialFrame) { + // Serial protocol with length info + if (buffer.length() < 4) + return 0; + mux = extractShort(buffer.data()); + const ushort len = extractShort(buffer.data() + 2); + return (buffer.size() >= len + 4) ? len : ushort(0); + } + // Frameless protocol without length info + const char delimiter = char(0x7e); + const int firstDelimiterPos = buffer.indexOf(delimiter); + // Regular message delimited by 0x7e..0x7e + if (firstDelimiterPos == 0) { + mux = MuxTrk; + const int endPos = buffer.indexOf(delimiter, firstDelimiterPos + 1); + return endPos != -1 ? endPos + 1 - firstDelimiterPos : 0; + } + // Some ASCII log message up to first delimiter or all + return firstDelimiterPos != -1 ? firstDelimiterPos : buffer.size(); +} + +bool extractResult(QByteArray *buffer, bool serialFrame, TrkResult *result, bool &linkEstablishmentMode, QByteArray *rawData) +{ + result->clear(); + if(rawData) + rawData->clear(); + ushort len = isValidTrkResult(*buffer, serialFrame, result->multiplex); + // handle receiving application output, which is not a regular command + const int delimiterPos = serialFrame ? 4 : 0; + if (linkEstablishmentMode) { + //when "hot connecting" a device, we can receive partial frames. + //this code resyncs by discarding data until a TRK frame is found + while (buffer->length() > delimiterPos + && result->multiplex != MuxTextTrace + && !(result->multiplex == MuxTrk && buffer->at(delimiterPos) == 0x7e)) { + buffer->remove(0,1); + len = isValidTrkResult(*buffer, serialFrame, result->multiplex); + } + } + if (!len) + return false; + if (buffer->at(delimiterPos) != 0x7e) { + result->isDebugOutput = true; + result->data = buffer->mid(delimiterPos, len); + buffer->remove(0, delimiterPos + len); + return true; + } + // FIXME: what happens if the length contains 0xfe? + // Assume for now that it passes unencoded! + const QByteArray data = decode7d(buffer->mid(delimiterPos + 1, len - 2)); + if(rawData) + *rawData = data; + buffer->remove(0, delimiterPos + len); + + byte sum = 0; + for (int i = 0; i < data.size(); ++i) // 3 = 2 * 0xfe + sum + sum += byte(data.at(i)); + if (sum != 0xff) + logMessage("*** CHECKSUM ERROR: " << byte(sum)); + + result->code = data.at(0); + result->token = data.at(1); + result->data = data.mid(2, data.size() - 3); + //logMessage(" REST BUF: " << stringFromArray(*buffer)); + //logMessage(" CURR DATA: " << stringFromArray(data)); + //QByteArray prefix = "READ BUF: "; + //logMessage((prefix + "HEADER: " + stringFromArray(header).toLatin1()).data()); + linkEstablishmentMode = false; //have received a good TRK packet, therefore in sync + return true; +} + +SYMBIANUTILS_EXPORT ushort extractShort(const char *data) +{ + return byte(data[0]) * 256 + byte(data[1]); +} + +SYMBIANUTILS_EXPORT uint extractInt(const char *data) +{ + uint res = byte(data[0]); + res *= 256; res += byte(data[1]); + res *= 256; res += byte(data[2]); + res *= 256; res += byte(data[3]); + return res; +} + +SYMBIANUTILS_EXPORT quint64 extractInt64(const char *data) +{ + quint64 res = byte(data[0]); + res <<= 8; res += byte(data[1]); + res <<= 8; res += byte(data[2]); + res <<= 8; res += byte(data[3]); + res <<= 8; res += byte(data[4]); + res <<= 8; res += byte(data[5]); + res <<= 8; res += byte(data[6]); + res <<= 8; res += byte(data[7]); + return res; +} + +SYMBIANUTILS_EXPORT QString quoteUnprintableLatin1(const QByteArray &ba) +{ + QString res; + char buf[10]; + for (int i = 0, n = ba.size(); i != n; ++i) { + const byte c = ba.at(i); + if (isprint(c)) { + res += c; + } else { + qsnprintf(buf, sizeof(buf) - 1, "\\%x", int(c)); + res += buf; + } + } + return res; +} + +SYMBIANUTILS_EXPORT void appendShort(QByteArray *ba, ushort s, Endianness endian) +{ + if (endian == BigEndian) { + ba->append(s / 256); + ba->append(s % 256); + } else { + ba->append(s % 256); + ba->append(s / 256); + } +} + +SYMBIANUTILS_EXPORT void appendInt(QByteArray *ba, uint i, Endianness endian) +{ + const uchar b3 = i % 256; i /= 256; + const uchar b2 = i % 256; i /= 256; + const uchar b1 = i % 256; i /= 256; + const uchar b0 = i; + ba->reserve(ba->size() + 4); + if (endian == BigEndian) { + ba->append(b0); + ba->append(b1); + ba->append(b2); + ba->append(b3); + } else { + ba->append(b3); + ba->append(b2); + ba->append(b1); + ba->append(b0); + } +} + +void appendString(QByteArray *ba, const QByteArray &str, Endianness endian, bool appendNullTerminator) +{ + const int fullSize = str.size() + (appendNullTerminator ? 1 : 0); + appendShort(ba, fullSize, endian); // count the terminating \0 + ba->append(str); + if (appendNullTerminator) + ba->append('\0'); +} + +void appendDateTime(QByteArray *ba, QDateTime dateTime, Endianness endian) +{ + // convert the QDateTime to UTC and append its representation to QByteArray + // format is the same as in FAT file system + dateTime = dateTime.toUTC(); + const QTime utcTime = dateTime.time(); + const QDate utcDate = dateTime.date(); + uint fatDateTime = (utcTime.hour() << 11 | utcTime.minute() << 5 | utcTime.second()/2) << 16; + fatDateTime |= (utcDate.year()-1980) << 9 | utcDate.month() << 5 | utcDate.day(); + appendInt(ba, fatDateTime, endian); +} + +QByteArray errorMessage(byte code) +{ + switch (code) { + case 0x00: return "No error"; + case 0x01: return "Generic error in CWDS message"; + case 0x02: return "Unexpected packet size in send msg"; + case 0x03: return "Internal error occurred in CWDS"; + case 0x04: return "Escape followed by frame flag"; + case 0x05: return "Bad FCS in packet"; + case 0x06: return "Packet too long"; + case 0x07: return "Sequence ID not expected (gap in sequence)"; + + case 0x10: return "Command not supported"; + case 0x11: return "Command param out of range"; + case 0x12: return "An option was not supported"; + case 0x13: return "Read/write to invalid memory"; + case 0x14: return "Read/write invalid registers"; + case 0x15: return "Exception occurred in CWDS"; + case 0x16: return "Targeted system or thread is running"; + case 0x17: return "Breakpoint resources (HW or SW) exhausted"; + case 0x18: return "Requested breakpoint conflicts with existing one"; + + case 0x20: return "General OS-related error"; + case 0x21: return "Request specified invalid process"; + case 0x22: return "Request specified invalid thread"; + } + return "Unknown error"; +} + +uint swapEndian(uint in) +{ + return (in>>24) | ((in<<8) & 0x00FF0000) | ((in>>8) & 0x0000FF00) | (in<<24); +} + +int TrkResult::errorCode() const +{ + // NAK means always error, else data sized 1 with a non-null element + const bool isNAK = code == 0xff; + if (data.size() != 1 && !isNAK) + return 0; + if (const int errorCode = data.at(0)) + return errorCode; + return isNAK ? 0xff : 0; +} + +QString TrkResult::errorString() const +{ + // NAK means always error, else data sized 1 with a non-null element + if (code == 0xff) + return "NAK"; + if (data.size() < 1) + return "Unknown error packet"; + return errorMessage(data.at(0)); +} + +} // namespace trk + diff --git a/src/runonphone/symbianutils/trkutils.h b/src/runonphone/symbianutils/trkutils.h new file mode 100644 index 000000000..c22df865a --- /dev/null +++ b/src/runonphone/symbianutils/trkutils.h @@ -0,0 +1,261 @@ +/**************************************************************************** +** +** 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 DEBUGGER_TRK_UTILS +#define DEBUGGER_TRK_UTILS + +#include "symbianutils_global.h" + +#include <QtCore/QByteArray> +#include <QtCore/QHash> +#include <QtCore/QStringList> +#include <QtCore/QVariant> + +QT_BEGIN_NAMESPACE +class QDateTime; +QT_END_NAMESPACE + +namespace trk { + +typedef unsigned char byte; +struct TrkResult; + +enum Command { + //meta commands + TrkPing = 0x00, + TrkConnect = 0x01, + TrkDisconnect = 0x02, + TrkReset = 0x03, + TrkVersions = 0x04, + TrkSupported = 0x05, + TrkCpuType = 0x06, + TrkConfigTransport = 0x07, + TrkVersions2 = 0x08, + TrkHostVersions = 0x09, + + //state commands + TrkReadMemory = 0x10, + TrkWriteMemory = 0x11, + TrkReadRegisters = 0x12, + TrkWriteRegisters = 0x13, + TrkFillMemory = 0x14, + TrkCopyMemory = 0x15, + TrkFlushCache = 0x16, + + //execution commands + TrkContinue = 0x18, + TrkStep = 0x19, + TrkStop = 0x1a, + TrkSetBreak = 0x1b, + TrkClearBreak = 0x1c, + TrkDownload = 0x1d, + TrkModifyBreakThread = 0x1e, + + //host -> target IO management + TrkNotifyFileInput = 0x20, + TrkBlockFileIo = 0x21, + + //host -> target os commands + TrkCreateItem = 0x40, + TrkDeleteItem = 0x41, + TrkReadInfo = 0x42, + TrkWriteInfo = 0x43, + + TrkWriteFile = 0x48, + TrkReadFile = 0x49, + TrkOpenFile = 0x4a, + TrkCloseFile = 0x4b, + TrkPositionFile = 0x4c, + TrkInstallFile = 0x4d, + TrkInstallFile2 = 0x4e, + + TrkPhoneSwVersion = 0x4f, + TrkPhoneName = 0x50, + TrkVersions3 = 0x51, + + //replies + TrkNotifyAck = 0x80, + TrkNotifyNak = 0xff, + + //target -> host notification + TrkNotifyStopped = 0x90, + TrkNotifyException = 0x91, + TrkNotifyInternalError = 0x92, + TrkNotifyStopped2 = 0x94, + + //target -> host OS notification + TrkNotifyCreated = 0xa0, + TrkNotifyDeleted = 0xa1, + TrkNotifyProcessorStarted = 0xa2, + TrkNotifyProcessorStandBy = 0xa6, + TrkNotifyProcessorReset = 0xa7, + + //target -> host support commands (these are defined but not implemented in TRK) + TrkDSWriteFile = 0xd0, + TrkDSReadFile = 0xd1, + TrkDSOpenFile = 0xd2, + TrkDSCloseFile = 0xd3, + TrkDSPositionFile = 0xd4 +}; + +enum DSOSItemTypes { + kDSOSProcessItem = 0x0000, + kDSOSThreadItem = 0x0001, + kDSOSDLLItem = 0x0002, + kDSOSAppItem = 0x0003, + kDSOSMemBlockItem = 0x0004, + kDSOSProcAttachItem = 0x0005, + kDSOSThreadAttachItem = 0x0006, + kDSOSProcAttach2Item = 0x0007, + kDSOSProcRunItem = 0x0008, + /* 0x0009 - 0x00ff reserved for general expansion */ + /* 0x0100 - 0xffff available for target-specific use */ +}; + +enum SerialMultiplexor { + MuxRaw = 0, + MuxTextTrace = 0x0102, + MuxTrk = 0x0190 +}; + +inline byte extractByte(const char *data) { return *data; } +SYMBIANUTILS_EXPORT ushort extractShort(const char *data); +SYMBIANUTILS_EXPORT uint extractInt(const char *data); +SYMBIANUTILS_EXPORT quint64 extractInt64(const char *data); + +SYMBIANUTILS_EXPORT QString quoteUnprintableLatin1(const QByteArray &ba); + +// produces "xx xx xx " +SYMBIANUTILS_EXPORT QString stringFromArray(const QByteArray &ba, int maxLen = - 1); + +enum Endianness +{ + LittleEndian, + BigEndian, + TargetByteOrder = BigEndian, +}; + +SYMBIANUTILS_EXPORT void appendShort(QByteArray *ba, ushort s, Endianness = TargetByteOrder); +SYMBIANUTILS_EXPORT void appendInt(QByteArray *ba, uint i, Endianness = TargetByteOrder); +SYMBIANUTILS_EXPORT void appendString(QByteArray *ba, const QByteArray &str, Endianness = TargetByteOrder, bool appendNullTerminator = true); + +struct SYMBIANUTILS_EXPORT Library +{ + Library(); + explicit Library(const TrkResult &r); + + QByteArray name; + uint codeseg; + uint dataseg; + //library addresses are valid for a given process (depending on memory model, they might be loaded at the same address in all processes or not) + uint pid; +}; + +struct SYMBIANUTILS_EXPORT TrkAppVersion +{ + TrkAppVersion(); + void reset(); + + int trkMajor; + int trkMinor; + int protocolMajor; + int protocolMinor; +}; + +struct SYMBIANUTILS_EXPORT Session +{ + Session(); + void reset(); + QString deviceDescription(unsigned verbose) const; + QString toString() const; + // Answer to qXfer::libraries + QByteArray gdbLibraryList() const; + // Answer to qsDllInfo, can be sent chunk-wise. + QByteArray gdbQsDllInfo(int start = 0, int count = -1) const; + + // Trk feedback + byte cpuMajor; + byte cpuMinor; + byte bigEndian; + byte defaultTypeSize; + byte fpTypeSize; + byte extended1TypeSize; + byte extended2TypeSize; + TrkAppVersion trkAppVersion; + uint pid; + uint mainTid; + uint tid; + uint codeseg; + uint dataseg; + QHash<uint, uint> addressToBP; + + typedef QList<Library> Libraries; + Libraries libraries; + + // Gdb request + QStringList modules; +}; + +struct SYMBIANUTILS_EXPORT TrkResult +{ + TrkResult(); + void clear(); + QString toString() const; + // 0 for no error. + int errorCode() const; + QString errorString() const; + + ushort multiplex; + byte code; + byte token; + QByteArray data; + QVariant cookie; + bool isDebugOutput; +}; + +SYMBIANUTILS_EXPORT QByteArray errorMessage(byte code); +SYMBIANUTILS_EXPORT QByteArray hexNumber(uint n, int digits = 0); +SYMBIANUTILS_EXPORT QByteArray hexxNumber(uint n, int digits = 0); // prepends '0x', too +SYMBIANUTILS_EXPORT uint swapEndian(uint in); + +} // namespace trk + +#endif // DEBUGGER_TRK_UTILS diff --git a/src/runonphone/symbianutils/trkutils_p.h b/src/runonphone/symbianutils/trkutils_p.h new file mode 100644 index 000000000..df9504c5e --- /dev/null +++ b/src/runonphone/symbianutils/trkutils_p.h @@ -0,0 +1,62 @@ +/**************************************************************************** +** +** 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 DEBUGGER_TRK_PRIVATE_UTILS +#define DEBUGGER_TRK_PRIVATE_UTILS + +#include "trkutils.h" +#include "symbianutils_global.h" + +QT_BEGIN_NAMESPACE +class QDateTime; +QT_END_NAMESPACE + +namespace trk { + +void appendDateTime(QByteArray *ba, QDateTime dateTime, Endianness = TargetByteOrder); +// returns a QByteArray containing optionally +// the serial frame [0x01 0x90 <len>] and 0x7e encoded7d(ba) 0x7e +QByteArray frameMessage(byte command, byte token, const QByteArray &data, bool serialFrame); +bool extractResult(QByteArray *buffer, bool serialFrame, TrkResult *r, bool& linkEstablishmentMode, QByteArray *rawData = 0); + +} // namespace trk + +#endif // DEBUGGER_TRK_PRIVATE_UTILS diff --git a/src/runonphone/trksignalhandler.cpp b/src/runonphone/trksignalhandler.cpp new file mode 100644 index 000000000..a2b8f6f27 --- /dev/null +++ b/src/runonphone/trksignalhandler.cpp @@ -0,0 +1,368 @@ +/**************************************************************************** +** +** 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 <QDebug> +#include <QCoreApplication> +#include <QObject> +#include <QFile> +#include <QDir> +#include "trksignalhandler.h" +#include "trkutils.h" + +class CrashState +{ +public: + uint pid; + uint tid; + QString crashReason; + uint crashPC; +}; + +class TrkSignalHandlerPrivate +{ + friend class TrkSignalHandler; +public: + TrkSignalHandlerPrivate(); + ~TrkSignalHandlerPrivate(); +private: + QTextStream out; + QTextStream err; + int loglevel; + int lastpercent; + QList<trk::Library> libraries; + QFile crashlogtextfile; + QFile crashstackfile; + QList<CrashState> queuedCrashes; + QList<int> dyingThreads; + QString crashlogPath; + bool crashlog; + bool terminateNeeded; +}; + +void TrkSignalHandler::copyingStarted() +{ + if (d->loglevel > 0) + d->out << "Copying..." << endl; +} + +void TrkSignalHandler::canNotConnect(const QString &errorMessage) +{ + d->err << "Cannot connect - " << errorMessage << endl; +} + +void TrkSignalHandler::canNotCreateFile(const QString &filename, const QString &errorMessage) +{ + d->err << "Cannot create file (" << filename << ") - " << errorMessage << endl; +} + +void TrkSignalHandler::canNotWriteFile(const QString &filename, const QString &errorMessage) +{ + d->err << "Cannot write file (" << filename << ") - " << errorMessage << endl; +} + +void TrkSignalHandler::canNotCloseFile(const QString &filename, const QString &errorMessage) +{ + d->err << "Cannot close file (" << filename << ") - " << errorMessage << endl; +} + +void TrkSignalHandler::installingStarted() +{ + if (d->loglevel > 0) + d->out << "Installing..." << endl; +} + +void TrkSignalHandler::canNotInstall(const QString &packageFilename, const QString &errorMessage) +{ + d->err << "Cannot install file (" << packageFilename << ") - " << errorMessage << endl; +} + +void TrkSignalHandler::installingFinished() +{ + if (d->loglevel > 0) + d->out << "Installing finished" << endl; +} + +void TrkSignalHandler::startingApplication() +{ + if (d->loglevel > 0) + d->out << "Starting app..." << endl; +} + +void TrkSignalHandler::applicationRunning(uint pid) +{ + Q_UNUSED(pid) + if (d->loglevel > 0) + d->out << "Running..." << endl; +} + +void TrkSignalHandler::canNotRun(const QString &errorMessage) +{ + d->err << "Cannot run - " << errorMessage << endl; +} + +void TrkSignalHandler::finished() +{ + if (d->loglevel > 0) + d->out << "Done." << endl; + QCoreApplication::quit(); +} + +void TrkSignalHandler::applicationOutputReceived(const QString &output) +{ + d->out << output << flush; +} + +void TrkSignalHandler::copyProgress(int percent) +{ + if (d->loglevel > 0) { + if (d->lastpercent == 0) + d->out << "[ ]\r[" << flush; + while (percent > d->lastpercent) { + d->out << QLatin1Char('#'); + d->lastpercent+=2; //because typical console is 80 chars wide + } + d->out.flush(); + if (percent==100) + d->out << endl; + } +} + +void TrkSignalHandler::stateChanged(int state) +{ + if (d->loglevel > 1) + d->out << "State" << state << endl; +} + +void TrkSignalHandler::setLogLevel(int level) +{ + d->loglevel = level; +} + +void TrkSignalHandler::setCrashLogging(bool enabled) +{ + d->crashlog = enabled; +} + +void TrkSignalHandler::setCrashLogPath(QString path) +{ + d->crashlogPath = path; +} + +bool lessThanCodeBase(const trk::Library& cs1, const trk::Library& cs2) +{ + return cs1.codeseg < cs2.codeseg; +} + +void TrkSignalHandler::stopped(uint pc, uint pid, uint tid, const QString& reason) +{ + d->err << "STOPPED: pc=" << hex << pc << " pid=" << pid + << " tid=" << tid << dec << " - " << reason << endl; + + if (d->crashlog) { + CrashState cs; + cs.pid = pid; + cs.tid = tid; + cs.crashPC = pc; + cs.crashReason = reason; + + if (d->dyingThreads.contains(tid)) { + if(d->queuedCrashes.isEmpty()) + emit terminate(); + else + d->terminateNeeded = true; + } else { + d->queuedCrashes.append(cs); + d->dyingThreads.append(tid); + + if (d->queuedCrashes.count() == 1) { + d->err << "Fetching registers and stack..." << endl; + emit getRegistersAndCallStack(pid, tid); + } + } + } + else + emit terminate(); +} + +void TrkSignalHandler::registersAndCallStackReadComplete(const QList<uint>& registers, const QByteArray& stack) +{ + CrashState cs = d->queuedCrashes.first(); + QDir dir(d->crashlogPath); + d->crashlogtextfile.setFileName(dir.filePath(QString("d_exc_%1.txt").arg(cs.tid))); + d->crashstackfile.setFileName(dir.filePath(QString("d_exc_%1.stk").arg(cs.tid))); + d->crashlogtextfile.open(QIODevice::WriteOnly); + QTextStream crashlog(&d->crashlogtextfile); + + crashlog << "-----------------------------------------------------------------------------" << endl; + crashlog << "EKA2 USER CRASH LOG" << endl; + crashlog << "Thread Name: " << QString("ProcessID-%1::ThreadID-%2").arg(cs.pid).arg(cs.tid) << endl; + crashlog << "Thread ID: " << cs.tid << endl; + //this is wrong, but TRK doesn't make stack limit available so we lie + crashlog << QString("User Stack %1-%2").arg(registers.at(13), 8, 16, QChar('0')).arg(registers.at(13) + stack.size(), 8, 16, QChar('0')) << endl; + //this is also wrong, but TRK doesn't give all information for exceptions + crashlog << QString("Panic: PC=%1 ").arg(cs.crashPC, 8, 16, QChar('0')) << cs.crashReason << endl; + crashlog << endl; + crashlog << "USER REGISTERS:" << endl; + crashlog << QString("CPSR=%1").arg(registers.at(16), 8, 16, QChar('0')) << endl; + for (int i=0;i<16;i+=4) { + crashlog << QString("r%1=%2 %3 %4 %5") + .arg(i, 2, 10, QChar('0')) + .arg(registers.at(i), 8, 16, QChar('0')) + .arg(registers.at(i+1), 8, 16, QChar('0')) + .arg(registers.at(i+2), 8, 16, QChar('0')) + .arg(registers.at(i+3), 8, 16, QChar('0')) << endl; + } + crashlog << endl; + + //emit info for post mortem debug + qSort(d->libraries.begin(), d->libraries.end(), lessThanCodeBase); + d->err << "Code Segments:" << endl; + crashlog << "CODE SEGMENTS:" << endl; + for(int i=0; i<d->libraries.count(); i++) { + const trk::Library& seg = d->libraries.at(i); + if(seg.pid != cs.pid) + continue; + if (d->loglevel > 1) { + d->err << QString("Code: %1 Data: %2 Name: ") + .arg(seg.codeseg, 8, 16, QChar('0')) + .arg(seg.dataseg, 8, 16, QChar('0')) + << seg.name << endl; + } + + //produce fake code segment end addresses since we don't get the real ones from TRK + uint end; + if (i+1 < d->libraries.count()) + end = d->libraries.at(i+1).codeseg - 1; + else + end = 0xFFFFFFFF; + + crashlog << QString("%1-%2 ") + .arg(seg.codeseg, 8, 16, QChar('0')) + .arg(end, 8, 16, QChar('0')) + << seg.name << endl; + } + + d->crashlogtextfile.close(); + + if (d->loglevel > 1) { + d->err << "Registers:" << endl; + for (int i=0;i<16;i++) { + d->err << QString("R%1: %2 ").arg(i, 2, 10, QChar('0')).arg(registers.at(i), 8, 16, QChar('0')); + if (i % 4 == 3) + d->err << endl; + } + d->err << QString("CPSR: %1").arg(registers.at(16), 8, 16, QChar('0')) << endl; + + d->err << "Stack:" << endl; + uint sp = registers.at(13); + for(int i=0; i<stack.size(); i+=16, sp+=16) { + d->err << QString("%1: ").arg(sp, 8, 16, QChar('0')); + d->err << trk::stringFromArray(stack.mid(i,16)); + d->err << endl; + } + } + d->crashstackfile.open(QIODevice::WriteOnly); + d->crashstackfile.write(stack); + d->crashstackfile.close(); + + if (d->loglevel > 0) + d->err << "Crash logs saved to " << d->crashlogtextfile.fileName() << " & " << d->crashstackfile.fileName() << endl; + + // resume the thread to allow Symbian OS to handle the panic normally. + // terminate when a non main thread is suspended reboots the phone (TRK bug) + emit resume(cs.pid, cs.tid); + + //fetch next crashed thread + d->queuedCrashes.removeFirst(); + if (d->queuedCrashes.count()) { + cs = d->queuedCrashes.first(); + d->err << "Fetching registers and stack..." << endl; + emit getRegistersAndCallStack(cs.pid, cs.tid); + } + else if (d->terminateNeeded) + emit terminate(); + +} + +void TrkSignalHandler::libraryLoaded(const trk::Library &lib) +{ + d->libraries << lib; +} + +void TrkSignalHandler::libraryUnloaded(const trk::Library &lib) +{ + for (QList<trk::Library>::iterator i = d->libraries.begin(); i != d->libraries.end(); i++) { + if((*i).name == lib.name && (*i).pid == lib.pid) + i = d->libraries.erase(i); + } +} + +void TrkSignalHandler::timeout() +{ + d->err << "FAILED: stopping test due to timeout" << endl; + emit terminate(); +} + +TrkSignalHandlerPrivate::TrkSignalHandlerPrivate() + : out(stdout), + err(stderr), + loglevel(0), + lastpercent(0), + terminateNeeded(false) +{ + +} + +TrkSignalHandlerPrivate::~TrkSignalHandlerPrivate() +{ + out.flush(); + err.flush(); +} + +TrkSignalHandler::TrkSignalHandler() + : d(new TrkSignalHandlerPrivate()) +{ +} + +TrkSignalHandler::~TrkSignalHandler() +{ + delete d; +} diff --git a/src/runonphone/trksignalhandler.h b/src/runonphone/trksignalhandler.h new file mode 100644 index 000000000..f8331800c --- /dev/null +++ b/src/runonphone/trksignalhandler.h @@ -0,0 +1,88 @@ +/**************************************************************************** +** +** 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 TRKSIGNALHANDLER_H +#define TRKSIGNALHANDLER_H +#include <QObject> +#include <QString> +#include "symbianutils/trkutils.h" + +class TrkSignalHandlerPrivate; +class TrkSignalHandler : public QObject +{ + Q_OBJECT +public slots: + void copyingStarted(); + 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 canNotInstall(const QString &packageFilename, const QString &errorMessage); + void installingFinished(); + void startingApplication(); + void applicationRunning(uint pid); + void canNotRun(const QString &errorMessage); + void finished(); + void applicationOutputReceived(const QString &output); + void copyProgress(int percent); + void stateChanged(int); + void stopped(uint pc, uint pid, uint tid, const QString& reason); + void timeout(); + void libraryLoaded(const trk::Library &lib); + void libraryUnloaded(const trk::Library &lib); + void registersAndCallStackReadComplete(const QList<uint>& registers, const QByteArray& stack); +signals: + void resume(uint pid, uint tid); + void stop(uint pid, uint tid); + void terminate(); + void getRegistersAndCallStack(uint pid, uint tid); +public: + TrkSignalHandler(); + ~TrkSignalHandler(); + void setLogLevel(int); + void setCrashLogging(bool); + void setCrashLogPath(QString); +private: + TrkSignalHandlerPrivate *d; +}; + +#endif // TRKSIGNALHANDLER_H |