1 /****************************************************************************
2 **
3 ** Copyright (C) 2015 The Qt Company Ltd.
4 ** Contact: http://www.qt.io/licensing/
5 **
6 ** This file is part of the tools applications of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** Commercial License Usage
10 ** Licensees holding valid commercial Qt licenses may use this file in
11 ** accordance with the commercial license agreement provided with the
12 ** Software or, alternatively, in accordance with the terms contained in
13 ** a written agreement between you and The Qt Company. For licensing terms
14 ** and conditions see http://www.qt.io/terms-conditions. For further
15 ** information use the contact form at http://www.qt.io/contact-us.
16 **
17 ** GNU Lesser General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU Lesser
19 ** General Public License version 2.1 or version 3 as published by the Free
20 ** Software Foundation and appearing in the file LICENSE.LGPLv21 and
21 ** LICENSE.LGPLv3 included in the packaging of this file. Please review the
22 ** following information to ensure the GNU Lesser General Public License
23 ** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
24 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
25 **
26 ** As a special exception, The Qt Company gives you certain additional
27 ** rights. These rights are described in The Qt Company LGPL Exception
28 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
29 **
30 ** GNU General Public License Usage
31 ** Alternatively, this file may be used under the terms of the GNU
32 ** General Public License version 3.0 as published by the Free Software
33 ** Foundation and appearing in the file LICENSE.GPL included in the
34 ** packaging of this file.  Please review the following information to
35 ** ensure the GNU General Public License version 3.0 requirements will be
36 ** met: http://www.gnu.org/copyleft/gpl.html.
37 **
38 ** $QT_END_LICENSE$
39 **
40 ****************************************************************************/
41 
42 #include "symbiandevicemanager.h"
43 #include "trkdevice.h"
44 #include "codadevice.h"
45 #include "virtualserialdevice.h"
46 
47 #include <QtCore/QCoreApplication>
48 #include <QtCore/QEvent>
49 #include <QtCore/QSettings>
50 #include <QtCore/QStringList>
51 #include <QtCore/QFileInfo>
52 #include <QtCore/QtDebug>
53 #include <QtCore/QTextStream>
54 #include <QtCore/QSharedData>
55 #include <QtCore/QScopedPointer>
56 #include <QtCore/QSignalMapper>
57 #include <QtCore/QThread>
58 #include <QtCore/QWaitCondition>
59 #include <QtCore/QTimer>
60 #include <QtCore/QDir>
61 
62 namespace SymbianUtils {
63 
64 enum { debug = 0 };
65 
66 static const char REGKEY_CURRENT_CONTROL_SET[] = "HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet";
67 static const char USBSER[] = "Services/usbser/Enum";
68 
69 const char *SymbianDeviceManager::linuxBlueToothDeviceRootC = "/dev/rfcomm";
70 
71 // ------------- SymbianDevice
72 class SymbianDeviceData : public QSharedData {
73 public:
74     SymbianDeviceData();
75     ~SymbianDeviceData();
76 
77     bool isOpen() const;
78     void forcedClose();
79 
80     QString portName;
81     QString friendlyName;
82     QString deviceDesc;
83     QString manufacturer;
84     QString additionalInformation;
85 
86     DeviceCommunicationType type;
87     QSharedPointer<trk::TrkDevice> device;
88     QSharedPointer<Coda::CodaDevice> codaDevice;
89     int deviceAcquired;
90 };
91 
SymbianDeviceData()92 SymbianDeviceData::SymbianDeviceData() :
93         type(SerialPortCommunication),
94         deviceAcquired(0)
95 {
96 }
97 
isOpen() const98 bool SymbianDeviceData::isOpen() const
99 {
100     if (device)
101         return device->isOpen();
102     if (codaDevice)
103         return codaDevice->device()->isOpen();
104     return false;
105 }
106 
~SymbianDeviceData()107 SymbianDeviceData::~SymbianDeviceData()
108 {
109     forcedClose();
110 }
111 
forcedClose()112 void SymbianDeviceData::forcedClose()
113 {
114     // Close the device when unplugging. Should devices be in 'acquired' state,
115     // their owners should hit on write failures.
116     // Apart from the <shared item> destructor, also called by the devicemanager
117     // to ensure it also happens if other shared instances are still around.
118     if (isOpen()) {
119         if (deviceAcquired)
120             qWarning("Device on '%s' unplugged while an operation is in progress.",
121                      qPrintable(portName));
122         if (device)
123             device->close();
124         else
125             codaDevice->device()->close();
126     }
127 }
128 
SymbianDevice(SymbianDeviceData * data)129 SymbianDevice::SymbianDevice(SymbianDeviceData *data) :
130     m_data(data)
131 {
132 }
133 
SymbianDevice()134 SymbianDevice::SymbianDevice() :
135     m_data(new SymbianDeviceData)
136 {
137 }
SymbianDevice(const SymbianDevice & rhs)138 SymbianDevice::SymbianDevice(const SymbianDevice &rhs) :
139         m_data(rhs.m_data)
140 {
141 }
142 
operator =(const SymbianDevice & rhs)143 SymbianDevice &SymbianDevice::operator=(const SymbianDevice &rhs)
144 {
145     if (this != &rhs)
146         m_data = rhs.m_data;
147     return *this;
148 }
149 
~SymbianDevice()150 SymbianDevice::~SymbianDevice()
151 {
152 }
153 
forcedClose()154 void SymbianDevice::forcedClose()
155 {
156     m_data->forcedClose();
157 }
158 
portName() const159 QString SymbianDevice::portName() const
160 {
161     return m_data->portName;
162 }
163 
friendlyName() const164 QString SymbianDevice::friendlyName() const
165 {
166     return m_data->friendlyName;
167 }
168 
additionalInformation() const169 QString SymbianDevice::additionalInformation() const
170 {
171     return m_data->additionalInformation;
172 }
173 
setAdditionalInformation(const QString & a)174 void SymbianDevice::setAdditionalInformation(const QString &a)
175 {
176     m_data->additionalInformation = a;
177 }
178 
acquireDevice()179 SymbianDevice::TrkDevicePtr SymbianDevice::acquireDevice()
180 {
181     if (debug)
182         qDebug() << "SymbianDevice::acquireDevice" << m_data->portName
183                 << "acquired: " << m_data->deviceAcquired << " open: " << isOpen();
184     if (isNull() || m_data->deviceAcquired)
185         return TrkDevicePtr();
186     //if port was opened for coda (but is not acquired) then close it first.
187     if (m_data->codaDevice)
188         m_data->codaDevice->device()->close();
189     if (m_data->device.isNull()) {
190         m_data->device = TrkDevicePtr(new trk::TrkDevice);
191         m_data->device->setPort(m_data->portName);
192         m_data->device->setSerialFrame(m_data->type == SerialPortCommunication);
193     }
194     m_data->deviceAcquired = 1;
195     return m_data->device;
196 }
197 
releaseDevice(TrkDevicePtr * ptr)198 void SymbianDevice::releaseDevice(TrkDevicePtr *ptr /* = 0 */)
199 {
200     if (debug)
201         qDebug() << "SymbianDevice::releaseDevice" << m_data->portName
202                 << " open: " << isOpen();
203     if (m_data->deviceAcquired) {
204         if (m_data->device->isOpen())
205             m_data->device->clearWriteQueue();
206         // Release if a valid pointer was passed in.
207         if (ptr && !ptr->isNull()) {
208             ptr->data()->disconnect();
209             *ptr = TrkDevicePtr();
210         }
211         m_data->deviceAcquired = 0;
212     } else {
213         qWarning("Internal error: Attempt to release device that is not acquired.");
214     }
215 }
216 
deviceDesc() const217 QString SymbianDevice::deviceDesc() const
218 {
219     return m_data->deviceDesc;
220 }
221 
manufacturer() const222 QString SymbianDevice::manufacturer() const
223 {
224     return m_data->manufacturer;
225 }
226 
type() const227 DeviceCommunicationType SymbianDevice::type() const
228 {
229     return m_data->type;
230 }
231 
isNull() const232 bool SymbianDevice::isNull() const
233 {
234     return m_data->portName.isEmpty();
235 }
236 
isOpen() const237 bool SymbianDevice::isOpen() const
238 {
239     return m_data->isOpen();
240 }
241 
toString() const242 QString SymbianDevice::toString() const
243 {
244     QString rc;
245     QTextStream str(&rc);
246     format(str);
247     return rc;
248 }
249 
format(QTextStream & str) const250 void SymbianDevice::format(QTextStream &str) const
251 {
252     str << (m_data->type == BlueToothCommunication ? "Bluetooth: " : "Serial: ")
253         << m_data->portName;
254     if (!m_data->friendlyName.isEmpty()) {
255         str << " (" << m_data->friendlyName;
256         if (!m_data->deviceDesc.isEmpty())
257           str << " / " << m_data->deviceDesc;
258         str << ')';
259     }
260     if (!m_data->manufacturer.isEmpty())
261         str << " [" << m_data->manufacturer << ']';
262 }
263 
264 // Compare by port and friendly name
compare(const SymbianDevice & rhs) const265 int SymbianDevice::compare(const SymbianDevice &rhs) const
266 {
267     if (const int prc = m_data->portName.compare(rhs.m_data->portName))
268         return prc;
269     if (const int frc = m_data->friendlyName.compare(rhs.m_data->friendlyName))
270         return frc;
271     return 0;
272 }
273 
operator <<(QDebug d,const SymbianDevice & cd)274 SYMBIANUTILS_EXPORT QDebug operator<<(QDebug d, const SymbianDevice &cd)
275 {
276     d.nospace() << cd.toString();
277     return d;
278 }
279 
280 // ------------- SymbianDeviceManagerPrivate
281 struct SymbianDeviceManagerPrivate {
SymbianDeviceManagerPrivateSymbianUtils::SymbianDeviceManagerPrivate282     SymbianDeviceManagerPrivate() : m_initialized(false), m_devicesLock(QMutex::Recursive) {}
283 
284     bool m_initialized;
285     SymbianDeviceManager::SymbianDeviceList m_devices;
286     QMutex m_devicesLock; // Used for protecting access to m_devices and serialising getCodaDevice/delayedClosePort
287     // The following 2 variables are needed to manage requests for a TCF port not coming from the main thread
288     int m_constructTcfPortEventType;
289     QMutex m_codaPortWaitMutex;
290 };
291 
292 class QConstructTcfPortEvent : public QEvent
293 {
294 public:
QConstructTcfPortEvent(QEvent::Type eventId,const QString & portName,CodaDevicePtr * device,QWaitCondition * waiter)295     QConstructTcfPortEvent(QEvent::Type eventId, const QString &portName, CodaDevicePtr *device, QWaitCondition *waiter) :
296         QEvent(eventId), m_portName(portName), m_device(device), m_waiter(waiter)
297        {}
298 
299     QString m_portName;
300     CodaDevicePtr* m_device;
301     QWaitCondition *m_waiter;
302 };
303 
304 
SymbianDeviceManager(QObject * parent)305 SymbianDeviceManager::SymbianDeviceManager(QObject *parent) :
306     QObject(parent),
307     d(new SymbianDeviceManagerPrivate)
308 {
309     d->m_constructTcfPortEventType = QEvent::registerEventType();
310 }
311 
~SymbianDeviceManager()312 SymbianDeviceManager::~SymbianDeviceManager()
313 {
314     delete d;
315 }
316 
devices() const317 SymbianDeviceManager::SymbianDeviceList SymbianDeviceManager::devices() const
318 {
319     ensureInitialized();
320     QMutexLocker lock(&d->m_devicesLock);
321     return d->m_devices;
322 }
323 
toString() const324 QString SymbianDeviceManager::toString() const
325 {
326     QMutexLocker lock(&d->m_devicesLock);
327     QString rc;
328     QTextStream str(&rc);
329     str << d->m_devices.size() << " devices:\n";
330     const int count = d->m_devices.size();
331     for (int i = 0; i < count; i++) {
332         str << '#' << i << ' ';
333         d->m_devices.at(i).format(str);
334         str << '\n';
335     }
336     return rc;
337 }
338 
findByPortName(const QString & p) const339 int SymbianDeviceManager::findByPortName(const QString &p) const
340 {
341     ensureInitialized();
342     const int count = d->m_devices.size();
343     for (int i = 0; i < count; i++)
344         if (d->m_devices.at(i).portName() == p)
345             return i;
346     return -1;
347 }
348 
friendlyNameForPort(const QString & port) const349 QString SymbianDeviceManager::friendlyNameForPort(const QString &port) const
350 {
351     QMutexLocker lock(&d->m_devicesLock);
352     const int idx = findByPortName(port);
353     return idx == -1 ? QString() : d->m_devices.at(idx).friendlyName();
354 }
355 
356 SymbianDeviceManager::TrkDevicePtr
acquireDevice(const QString & port)357         SymbianDeviceManager::acquireDevice(const QString &port)
358 {
359     ensureInitialized();
360     const int idx = findByPortName(port);
361     if (idx == -1) {
362         qWarning("Attempt to acquire device '%s' that does not exist.", qPrintable(port));
363         if (debug)
364             qDebug() << *this;
365         return TrkDevicePtr();
366       }
367     const TrkDevicePtr rc = d->m_devices[idx].acquireDevice();
368     if (debug)
369         qDebug() << "SymbianDeviceManager::acquireDevice" << port << " returns " << !rc.isNull();
370     return rc;
371 }
372 
getCodaDevice(const QString & port)373 CodaDevicePtr SymbianDeviceManager::getCodaDevice(const QString &port)
374 {
375     ensureInitialized();
376     QMutexLocker lock(&d->m_devicesLock);
377     const int idx = findByPortName(port);
378     if (idx == -1) {
379         qWarning("Attempt to acquire device '%s' that does not exist.", qPrintable(port));
380         if (debug)
381             qDebug() << *this;
382         return CodaDevicePtr();
383     }
384     SymbianDevice& device = d->m_devices[idx];
385     if (device.m_data->device && device.m_data->device.data()->isOpen()) {
386         qWarning("Attempting to open a port '%s' that is configured for TRK!", qPrintable(port));
387         return CodaDevicePtr();
388     }
389     CodaDevicePtr& devicePtr = device.m_data->codaDevice;
390     if (devicePtr.isNull() || !devicePtr->device()->isOpen()) {
391         // Check we instanciate in the correct thread - we can't afford to create the CodaDevice (and more specifically, open the VirtualSerialDevice) in a thread that isn't guaranteed to be long-lived.
392         // Therefore, if we're not in SymbianDeviceManager's thread, rejig things so it's opened in the main thread
393         if (QThread::currentThread() != thread()) {
394             // SymbianDeviceManager is owned by the main thread
395             d->m_codaPortWaitMutex.lock();
396             QWaitCondition waiter;
397             QCoreApplication::postEvent(this, new QConstructTcfPortEvent((QEvent::Type)d->m_constructTcfPortEventType, port, &devicePtr, &waiter));
398             waiter.wait(&d->m_codaPortWaitMutex);
399             // When the wait returns (due to the wakeAll in SymbianDeviceManager::customEvent), the CodaDevice will be fully set up
400             d->m_codaPortWaitMutex.unlock();
401         } else {
402             // We're in the main thread, just set it up directly
403             constructCodaPort(devicePtr, port);
404         }
405     // We still carry on in the case we failed to open so the client can access the IODevice's errorString()
406     }
407     if (devicePtr->device()->isOpen())
408         device.m_data->deviceAcquired++;
409     return devicePtr;
410 }
411 
constructCodaPort(CodaDevicePtr & device,const QString & portName)412 void SymbianDeviceManager::constructCodaPort(CodaDevicePtr& device, const QString& portName)
413 {
414     QMutexLocker locker(&d->m_codaPortWaitMutex);
415     if (device.isNull()) {
416         device = QSharedPointer<Coda::CodaDevice>(new Coda::CodaDevice);
417         const QSharedPointer<SymbianUtils::VirtualSerialDevice> serialDevice(new SymbianUtils::VirtualSerialDevice(portName));
418         device->setSerialFrame(true);
419         device->setDevice(serialDevice);
420     }
421     if (!device->device()->isOpen()) {
422         bool ok = device->device().staticCast<SymbianUtils::VirtualSerialDevice>()->open(QIODevice::ReadWrite);
423         if (!ok && debug) {
424             qDebug("SymbianDeviceManager: Failed to open port %s", qPrintable(portName));
425         }
426     }
427 }
428 
customEvent(QEvent * event)429 void SymbianDeviceManager::customEvent(QEvent *event)
430 {
431     if (event->type() == d->m_constructTcfPortEventType) {
432         QConstructTcfPortEvent* constructEvent = static_cast<QConstructTcfPortEvent*>(event);
433         constructCodaPort(*constructEvent->m_device, constructEvent->m_portName);
434         constructEvent->m_waiter->wakeAll(); // Should only ever be one thing waiting on this
435     }
436 }
437 
releaseCodaDevice(CodaDevicePtr & port)438 void SymbianDeviceManager::releaseCodaDevice(CodaDevicePtr &port)
439 {
440     if (port) {
441         QMutexLocker(&d->m_devicesLock);
442         // Check if this was the last reference to the port, if so close it after a short delay
443         foreach (const SymbianDevice& device, d->m_devices) {
444             if (device.m_data->codaDevice.data() == port.data()) {
445                 if (device.m_data->deviceAcquired > 0)
446                     device.m_data->deviceAcquired--;
447                 if (device.m_data->deviceAcquired == 0) {
448                     if (debug)
449                         qDebug("Starting timer to close port %s", qPrintable(device.m_data->portName));
450                     QTimer::singleShot(1000, this, SLOT(delayedClosePort()));
451                 }
452                 break;
453             }
454         }
455         port.clear();
456     }
457 }
458 
delayedClosePort()459 void SymbianDeviceManager::delayedClosePort()
460 {
461     // Find any coda ports that are still open but have a reference count of zero, and delete them
462     QMutexLocker(&d->m_devicesLock);
463     foreach (const SymbianDevice& device, d->m_devices) {
464         Coda::CodaDevice* codaDevice = device.m_data->codaDevice.data();
465         if (codaDevice && device.m_data->deviceAcquired == 0 && codaDevice->device()->isOpen()) {
466             if (debug)
467                 qDebug("Closing device %s", qPrintable(device.m_data->portName));
468             device.m_data->codaDevice->device()->close();
469         }
470     }
471 }
472 
update()473 void SymbianDeviceManager::update()
474 {
475     update(true);
476 }
477 
releaseDevice(const QString & port)478 void SymbianDeviceManager::releaseDevice(const QString &port)
479 {
480     const int idx = findByPortName(port);
481     if (debug)
482         qDebug() << "SymbianDeviceManager::releaseDevice" << port << idx << sender();
483     if (idx != -1)
484         d->m_devices[idx].releaseDevice();
485     else
486         qWarning("Attempt to release non-existing device %s.", qPrintable(port));
487 }
488 
setAdditionalInformation(const QString & port,const QString & ai)489 void SymbianDeviceManager::setAdditionalInformation(const QString &port, const QString &ai)
490 {
491     const int idx = findByPortName(port);
492     if (idx != -1)
493         d->m_devices[idx].setAdditionalInformation(ai);
494 }
495 
ensureInitialized() const496 void SymbianDeviceManager::ensureInitialized() const
497 {
498     if (!d->m_initialized) // Flag is set in update()
499         const_cast<SymbianDeviceManager*>(this)->update(false);
500 }
501 
update(bool emitSignals)502 void SymbianDeviceManager::update(bool emitSignals)
503 {
504     QMutexLocker lock(&d->m_devicesLock);
505 
506     static int n = 0;
507     typedef SymbianDeviceList::iterator SymbianDeviceListIterator;
508 
509     if (debug)
510         qDebug(">SerialDeviceLister::update(#%d, signals=%d)\n%s", n++, int(emitSignals),
511                qPrintable(toString()));
512 
513     d->m_initialized = true;
514     // Get ordered new list
515     SymbianDeviceList newDevices = serialPorts() + blueToothDevices();
516     if (newDevices.size() > 1)
517         qStableSort(newDevices.begin(), newDevices.end());
518     if (d->m_devices == newDevices) { // Happy, nothing changed.
519         if (debug)
520             qDebug("<SerialDeviceLister::update: unchanged\n");
521         return;
522     }
523     // Merge the lists and emit the respective added/removed signals, assuming
524     // no one can plug a different device on the same port at the speed of lightning
525     SymbianDeviceList removedDevices;
526     if (!d->m_devices.isEmpty()) {
527         // Find deleted devices
528         for (SymbianDeviceListIterator oldIt = d->m_devices.begin(); oldIt != d->m_devices.end(); ) {
529             if (newDevices.contains(*oldIt)) {
530                 ++oldIt;
531             } else {
532                 SymbianDevice toBeDeleted = *oldIt;
533                 toBeDeleted.forcedClose();
534                 oldIt = d->m_devices.erase(oldIt);
535                 removedDevices.append(toBeDeleted);
536             }
537         }
538     }
539     SymbianDeviceList addedDevices;
540     if (!newDevices.isEmpty()) {
541         // Find new devices and insert in order
542         foreach(const SymbianDevice &newDevice, newDevices) {
543             if (!d->m_devices.contains(newDevice)) {
544                 d->m_devices.append(newDevice);
545                 addedDevices.append(newDevice);
546             }
547         }
548         if (d->m_devices.size() > 1)
549             qStableSort(d->m_devices.begin(), d->m_devices.end());
550     }
551 
552     lock.unlock();
553     if (emitSignals) {
554         foreach (const SymbianDevice &device, removedDevices) {
555             emit deviceRemoved(device);
556         }
557         foreach (const SymbianDevice &device, addedDevices) {
558             emit deviceAdded(device);
559         }
560         emit updated();
561     }
562 
563     if (debug)
564         qDebug("<SerialDeviceLister::update\n%s\n", qPrintable(toString()));
565 }
566 
serialPorts() const567 SymbianDeviceManager::SymbianDeviceList SymbianDeviceManager::serialPorts() const
568 {
569     SymbianDeviceList rc;
570 #ifdef Q_OS_WIN
571     const QSettings registry(REGKEY_CURRENT_CONTROL_SET, QSettings::NativeFormat);
572     const QString usbSerialRootKey = QLatin1String(USBSER) + QLatin1Char('/');
573     const int count = registry.value(usbSerialRootKey + QLatin1String("Count")).toInt();
574     for (int i = 0; i < count; ++i) {
575         QString driver = registry.value(usbSerialRootKey + QString::number(i)).toString();
576         if (driver.contains(QLatin1String("JAVACOMM"))) {
577             driver.replace(QLatin1Char('\\'), QLatin1Char('/'));
578             const QString driverRootKey = QLatin1String("Enum/") + driver + QLatin1Char('/');
579             if (debug > 1)
580                 qDebug() << "SerialDeviceLister::serialPorts(): Checking " << i << count
581                          << REGKEY_CURRENT_CONTROL_SET << usbSerialRootKey << driverRootKey;
582             QScopedPointer<SymbianDeviceData> device(new SymbianDeviceData);
583             device->type = SerialPortCommunication;
584             device->friendlyName = registry.value(driverRootKey + QLatin1String("FriendlyName")).toString();
585             device->portName = registry.value(driverRootKey + QLatin1String("Device Parameters/PortName")).toString();
586             device->deviceDesc = registry.value(driverRootKey + QLatin1String("DeviceDesc")).toString();
587             device->manufacturer = registry.value(driverRootKey + QLatin1String("Mfg")).toString();
588             rc.append(SymbianDevice(device.take()));
589         }
590     }
591 #endif
592     return rc;
593 }
594 
blueToothDevices() const595 SymbianDeviceManager::SymbianDeviceList SymbianDeviceManager::blueToothDevices() const
596 {
597     SymbianDeviceList rc;
598 #if defined(Q_OS_UNIX) && !defined(Q_OS_MAC)
599     // Bluetooth devices are created on connection. List the existing ones
600     // or at least the first one.
601     const QString prefix = QLatin1String(linuxBlueToothDeviceRootC);
602     const QString blueToothfriendlyFormat = QLatin1String("Bluetooth device (%1)");
603     for (int d = 0; d < 4; d++) {
604         QScopedPointer<SymbianDeviceData> device(new SymbianDeviceData);
605         device->type = BlueToothCommunication;
606         device->portName = prefix + QString::number(d);
607         if (d == 0 || QFileInfo(device->portName).exists()) {
608             device->friendlyName = blueToothfriendlyFormat.arg(device->portName);
609             rc.push_back(SymbianDevice(device.take()));
610         }
611     }
612     // New kernel versions support /dev/ttyUSB0, /dev/ttyUSB1. Trk responds
613     // on the latter (usually), try first.
614     static const char *usbTtyDevices[] = {
615         "/dev/ttyUSB3", "/dev/ttyUSB2", "/dev/ttyUSB1", "/dev/ttyUSB0",
616         "/dev/ttyACM3", "/dev/ttyACM2", "/dev/ttyACM1", "/dev/ttyACM0"};
617     const int usbTtyCount = sizeof(usbTtyDevices)/sizeof(const char *);
618     for (int d = 0; d < usbTtyCount; d++) {
619         const QString ttyUSBDevice = QLatin1String(usbTtyDevices[d]);
620         if (QFileInfo(ttyUSBDevice).exists()) {
621             SymbianDeviceData *device = new SymbianDeviceData;
622             device->type = SerialPortCommunication;
623             device->portName = ttyUSBDevice;
624             device->friendlyName = QString::fromLatin1("USB/Serial device (%1)").arg(device->portName);
625             rc.push_back(SymbianDevice(device));
626         }
627     }
628 #endif
629 #if defined(Q_OS_MAC)
630     QDir dir("/dev");
631     QStringList filters;
632     filters << "cu.usbmodem*";
633     dir.setNameFilters(filters);
634     QStringList entries = dir.entryList(QDir::System, QDir::Name);
635     foreach (const QString &dev, entries) {
636         SymbianDeviceData *device = new SymbianDeviceData;
637         device->type = SerialPortCommunication;
638         device->portName = dir.filePath(dev);
639         device->friendlyName = tr("USB/Serial device (%1)").arg(device->portName);
640         rc.push_back(SymbianDevice(device));
641     }
642 #endif
643     return rc;
644 }
645 
Q_GLOBAL_STATIC(SymbianDeviceManager,symbianDeviceManager)646 Q_GLOBAL_STATIC(SymbianDeviceManager, symbianDeviceManager)
647 
648 SymbianDeviceManager *SymbianDeviceManager::instance()
649 {
650     return symbianDeviceManager();
651 }
652 
operator <<(QDebug d,const SymbianDeviceManager & sdm)653 SYMBIANUTILS_EXPORT QDebug operator<<(QDebug d, const SymbianDeviceManager &sdm)
654 {
655     d.nospace() << sdm.toString();
656     return d;
657 }
658 
getOstChannel(const QString & port,uchar channelId)659 OstChannel *SymbianDeviceManager::getOstChannel(const QString &port, uchar channelId)
660 {
661     CodaDevicePtr coda = getCodaDevice(port);
662     if (coda.isNull() || !coda->device()->isOpen())
663         return 0;
664     return new OstChannel(coda, channelId);
665 }
666 
667 struct OstChannelPrivate
668 {
669     CodaDevicePtr m_codaPtr;
670     QByteArray m_dataBuffer;
671     uchar m_channelId;
672     bool m_hasReceivedData;
673 };
674 
OstChannel(const CodaDevicePtr & codaPtr,uchar channelId)675 OstChannel::OstChannel(const CodaDevicePtr &codaPtr, uchar channelId)
676     : d(new OstChannelPrivate)
677 {
678     d->m_codaPtr = codaPtr;
679     d->m_channelId = channelId;
680     d->m_hasReceivedData = false;
681     connect(codaPtr.data(), SIGNAL(unknownEvent(uchar, QByteArray)), this, SLOT(ostDataReceived(uchar,QByteArray)));
682     connect(codaPtr->device().data(), SIGNAL(aboutToClose()), this, SLOT(deviceAboutToClose()));
683     QIODevice::open(ReadWrite|Unbuffered);
684 }
685 
close()686 void OstChannel::close()
687 {
688     QIODevice::close();
689     if (d && d->m_codaPtr.data()) {
690         disconnect(d->m_codaPtr.data(), 0, this, 0);
691         SymbianDeviceManager::instance()->releaseCodaDevice(d->m_codaPtr);
692     }
693 }
694 
~OstChannel()695 OstChannel::~OstChannel()
696 {
697     close();
698     delete d;
699 }
700 
flush()701 void OstChannel::flush()
702 {
703     //TODO d->m_codaPtr->device()-
704 }
705 
bytesAvailable() const706 qint64 OstChannel::bytesAvailable() const
707 {
708     return d->m_dataBuffer.size();
709 }
710 
isSequential() const711 bool OstChannel::isSequential() const
712 {
713     return true;
714 }
715 
readData(char * data,qint64 maxSize)716 qint64 OstChannel::readData(char *data, qint64 maxSize)
717 {
718     qint64 amount = qMin(maxSize, (qint64)d->m_dataBuffer.size());
719     qMemCopy(data, d->m_dataBuffer.constData(), amount);
720     d->m_dataBuffer.remove(0, amount);
721     return amount;
722 }
723 
writeData(const char * data,qint64 maxSize)724 qint64 OstChannel::writeData(const char *data, qint64 maxSize)
725 {
726     static const qint64 KMaxOstPayload = 1024;
727     // If necessary, split the packet up
728     while (maxSize) {
729         QByteArray dataBuf = QByteArray::fromRawData(data, qMin(KMaxOstPayload, maxSize));
730         d->m_codaPtr->writeCustomData(d->m_channelId, dataBuf);
731         data += dataBuf.length();
732         maxSize -= dataBuf.length();
733     }
734     return maxSize;
735 }
736 
ostDataReceived(uchar channelId,const QByteArray & aData)737 void OstChannel::ostDataReceived(uchar channelId, const QByteArray &aData)
738 {
739     if (channelId == d->m_channelId) {
740         d->m_hasReceivedData = true;
741         d->m_dataBuffer.append(aData);
742         emit readyRead();
743     }
744 }
745 
codaDevice() const746 Coda::CodaDevice& OstChannel::codaDevice() const
747 {
748     return *d->m_codaPtr;
749 }
750 
hasReceivedData() const751 bool OstChannel::hasReceivedData() const
752 {
753     return isOpen() && d->m_hasReceivedData;
754 }
755 
deviceAboutToClose()756 void OstChannel::deviceAboutToClose()
757 {
758     close();
759 }
760 
761 } // namespace SymbianUtils
762