/**************************************************************************** | |
** | |
** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). | |
** All rights reserved. | |
** Contact: Nokia Corporation (qt-info@nokia.com) | |
** | |
** This file is part of the tools applications of the Qt Toolkit. | |
** | |
** $QT_BEGIN_LICENSE:LGPL$ | |
** GNU Lesser General Public License Usage | |
** This file may be used under the terms of the GNU Lesser General Public | |
** License version 2.1 as published by the Free Software Foundation and | |
** appearing in the file LICENSE.LGPL included in the packaging of this | |
** file. Please review the following information to ensure the GNU Lesser | |
** General Public License version 2.1 requirements will be met: | |
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. | |
** | |
** In addition, as a special exception, Nokia gives you certain additional | |
** rights. These rights are described in the Nokia Qt LGPL Exception | |
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. | |
** | |
** GNU General Public License Usage | |
** Alternatively, this file may be used under the terms of the GNU General | |
** Public License version 3.0 as published by the Free Software Foundation | |
** and appearing in the file LICENSE.GPL included in the packaging of this | |
** file. Please review the following information to ensure the GNU General | |
** Public License version 3.0 requirements will be met: | |
** http://www.gnu.org/copyleft/gpl.html. | |
** | |
** Other Usage | |
** Alternatively, this file may be used in accordance with the terms and | |
** conditions contained in a signed written agreement between you and Nokia. | |
** | |
** | |
** | |
** | |
** | |
** $QT_END_LICENSE$ | |
** | |
****************************************************************************/ | |
#include "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 |