diff options
Diffstat (limited to 'src/serialport')
27 files changed, 7941 insertions, 0 deletions
diff --git a/src/serialport/qt4support/install-helper.pri b/src/serialport/qt4support/install-helper.pri new file mode 100644 index 0000000..657dbd6 --- /dev/null +++ b/src/serialport/qt4support/install-helper.pri @@ -0,0 +1,25 @@ +SERIALPORT_PROJECT_INCLUDEDIR = $$SERIALPORT_BUILD_ROOT/include/QtAddOnSerialPort +SERIALPORT_PROJECT_INCLUDEDIR ~=s,/,$$QMAKE_DIR_SEP, + +system("$$QMAKE_MKDIR $$SERIALPORT_PROJECT_INCLUDEDIR") + +for(header_file, PUBLIC_HEADERS) { + header_file ~=s,/,$$QMAKE_DIR_SEP, + system("$$QMAKE_COPY $${header_file} $$SERIALPORT_PROJECT_INCLUDEDIR") +} + +target_headers.files = $$PUBLIC_HEADERS +target_headers.path = $$[QT_INSTALL_PREFIX]/include/QtAddOnSerialPort +INSTALLS += target_headers + +mkspecs_features.files = $$SERIALPORT_PROJECT_ROOT/src/serialport/qt4support/serialport.prf +mkspecs_features.path = $$[QT_INSTALL_DATA]/mkspecs/features +INSTALLS += mkspecs_features + +win32 { + dlltarget.path = $$[QT_INSTALL_BINS] + INSTALLS += dlltarget +} + +target.path = $$[QT_INSTALL_LIBS] +INSTALLS += target diff --git a/src/serialport/qt4support/qringbuffer_p.h b/src/serialport/qt4support/qringbuffer_p.h new file mode 100644 index 0000000..65eb8d7 --- /dev/null +++ b/src/serialport/qt4support/qringbuffer_p.h @@ -0,0 +1,451 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/ +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this +** file. Please review the following information to ensure the GNU Lesser +** General Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QRINGBUFFER_P_H +#define QRINGBUFFER_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of a number of Qt sources files. This header file may change from +// version to version without notice, or even be removed. +// +// We mean it. +// + +#include <QtCore/qbytearray.h> +#include <QtCore/qlist.h> + +QT_BEGIN_NAMESPACE + +class QRingBuffer +{ +public: + explicit inline QRingBuffer(int growth = 4096) : basicBlockSize(growth) { + buffers << QByteArray(); + clear(); + } + + inline int nextDataBlockSize() const { + return (tailBuffer == 0 ? tail : buffers.first().size()) - head; + } + + inline const char *readPointer() const { + return buffers.isEmpty() ? 0 : (buffers.first().constData() + head); + } + + // access the bytes at a specified position + // the out-variable length will contain the amount of bytes readable + // from there, e.g. the amount still the same QByteArray + inline const char *readPointerAtPosition(qint64 pos, qint64 &length) const { + if (buffers.isEmpty()) { + length = 0; + return 0; + } + + if (pos >= bufferSize) { + length = 0; + return 0; + } + + // special case: it is in the first buffer + int nextDataBlockSizeValue = nextDataBlockSize(); + if (pos - head < nextDataBlockSizeValue) { + length = nextDataBlockSizeValue - pos; + return buffers.at(0).constData() + head + pos; + } + + // special case: we only had one buffer and tried to read over it + if (buffers.length() == 1) { + length = 0; + return 0; + } + + // skip the first + pos -= nextDataBlockSizeValue; + + // normal case: it is somewhere in the second to the-one-before-the-tailBuffer + for (int i = 1; i < tailBuffer; i++) { + if (pos >= buffers[i].size()) { + pos -= buffers[i].size(); + continue; + } + + length = buffers[i].length() - pos; + return buffers[i].constData() + pos; + } + + // it is in the tail buffer + length = tail - pos; + return buffers[tailBuffer].constData() + pos; + } + + inline void free(int bytes) { + bufferSize -= bytes; + if (bufferSize < 0) + bufferSize = 0; + + for (;;) { + int nextBlockSize = nextDataBlockSize(); + if (bytes < nextBlockSize) { + head += bytes; + if (head == tail && tailBuffer == 0) + head = tail = 0; + break; + } + + bytes -= nextBlockSize; + if (buffers.count() == 1) { + if (buffers.at(0).size() != basicBlockSize) + buffers[0].resize(basicBlockSize); + head = tail = 0; + tailBuffer = 0; + break; + } + + buffers.removeAt(0); + --tailBuffer; + head = 0; + } + + if (isEmpty()) + clear(); // try to minify/squeeze us + } + + inline char *reserve(int bytes) { + // if this is a fresh empty QRingBuffer + if (bufferSize == 0) { + buffers[0].resize(qMax(basicBlockSize, bytes)); + bufferSize += bytes; + tail = bytes; + return buffers[tailBuffer].data(); + } + + bufferSize += bytes; + + // if there is already enough space, simply return. + if (tail + bytes <= buffers.at(tailBuffer).size()) { + char *writePtr = buffers[tailBuffer].data() + tail; + tail += bytes; + return writePtr; + } + + // if our buffer isn't half full yet, simply resize it. + if (tail < buffers.at(tailBuffer).size() / 2) { + buffers[tailBuffer].resize(tail + bytes); + char *writePtr = buffers[tailBuffer].data() + tail; + tail += bytes; + return writePtr; + } + + // shrink this buffer to its current size + buffers[tailBuffer].resize(tail); + + // create a new QByteArray with the right size + buffers << QByteArray(); + ++tailBuffer; + buffers[tailBuffer].resize(qMax(basicBlockSize, bytes)); + tail = bytes; + return buffers[tailBuffer].data(); + } + + inline void truncate(int pos) { + if (pos < size()) + chop(size() - pos); + } + + inline void chop(int bytes) { + bufferSize -= bytes; + if (bufferSize < 0) + bufferSize = 0; + + for (;;) { + // special case: head and tail are in the same buffer + if (tailBuffer == 0) { + tail -= bytes; + if (tail <= head) + tail = head = 0; + return; + } + + if (bytes <= tail) { + tail -= bytes; + return; + } + + bytes -= tail; + buffers.removeAt(tailBuffer); + + --tailBuffer; + tail = buffers.at(tailBuffer).size(); + } + + if (isEmpty()) + clear(); // try to minify/squeeze us + } + + inline bool isEmpty() const { + return tailBuffer == 0 && tail == 0; + } + + inline int getChar() { + if (isEmpty()) + return -1; + char c = *readPointer(); + free(1); + return int(uchar(c)); + } + + inline void putChar(char c) { + char *ptr = reserve(1); + *ptr = c; + } + + inline void ungetChar(char c) { + --head; + if (head < 0) { + buffers.prepend(QByteArray()); + buffers[0].resize(basicBlockSize); + head = basicBlockSize - 1; + ++tailBuffer; + } + buffers[0][head] = c; + ++bufferSize; + } + + inline int size() const { + return bufferSize; + } + + inline void clear() { + buffers.erase(buffers.begin() + 1, buffers.end()); + buffers[0].resize(0); + buffers[0].squeeze(); + + head = tail = 0; + tailBuffer = 0; + bufferSize = 0; + } + + inline int indexOf(char c) const { + int index = 0; + for (int i = 0; i < buffers.size(); ++i) { + int start = 0; + int end = buffers.at(i).size(); + + if (i == 0) + start = head; + if (i == tailBuffer) + end = tail; + const char *ptr = buffers.at(i).data() + start; + for (int j = start; j < end; ++j) { + if (*ptr++ == c) + return index; + ++index; + } + } + return -1; + } + + inline int indexOf(char c, int maxLength) const { + int index = 0; + int remain = qMin(size(), maxLength); + for (int i = 0; remain && i < buffers.size(); ++i) { + int start = 0; + int end = buffers.at(i).size(); + + if (i == 0) + start = head; + if (i == tailBuffer) + end = tail; + if (remain < end - start) { + end = start + remain; + remain = 0; + } else { + remain -= end - start; + } + const char *ptr = buffers.at(i).data() + start; + for (int j = start; j < end; ++j) { + if (*ptr++ == c) + return index; + ++index; + } + } + return -1; + } + + inline int read(char *data, int maxLength) { + int bytesToRead = qMin(size(), maxLength); + int readSoFar = 0; + while (readSoFar < bytesToRead) { + const char *ptr = readPointer(); + int bytesToReadFromThisBlock = qMin(bytesToRead - readSoFar, nextDataBlockSize()); + if (data) + memcpy(data + readSoFar, ptr, bytesToReadFromThisBlock); + readSoFar += bytesToReadFromThisBlock; + free(bytesToReadFromThisBlock); + } + return readSoFar; + } + + inline QByteArray read(int maxLength) { + QByteArray tmp; + tmp.resize(qMin(maxLength, size())); + read(tmp.data(), tmp.size()); + return tmp; + } + + inline QByteArray readAll() { + return read(size()); + } + + // read an unspecified amount (will read the first buffer) + inline QByteArray read() { + if (bufferSize == 0) + return QByteArray(); + + // multiple buffers, just take the first one + if (head == 0 && tailBuffer != 0) { + QByteArray qba = buffers.takeFirst(); + --tailBuffer; + bufferSize -= qba.length(); + return qba; + } + + // one buffer with good value for head. Just take it. + if (head == 0 && tailBuffer == 0) { + QByteArray qba = buffers.takeFirst(); + qba.resize(tail); + buffers << QByteArray(); + bufferSize = 0; + tail = 0; + return qba; + } + + // Bad case: We have to memcpy. + // We can avoid by initializing the QRingBuffer with basicBlockSize of 0 + // and only using this read() function. + QByteArray qba(readPointer(), nextDataBlockSize()); + buffers.removeFirst(); + head = 0; + if (tailBuffer == 0) { + buffers << QByteArray(); + tail = 0; + } else { + --tailBuffer; + } + bufferSize -= qba.length(); + return qba; + } + + // append a new buffer to the end + inline void append(const QByteArray &qba) { + buffers[tailBuffer].resize(tail); + buffers << qba; + ++tailBuffer; + tail = qba.length(); + bufferSize += qba.length(); + } + + inline QByteArray peek(int maxLength) const { + int bytesToRead = qMin(size(), maxLength); + if (maxLength <= 0) + return QByteArray(); + QByteArray ret; + ret.resize(bytesToRead); + int readSoFar = 0; + for (int i = 0; readSoFar < bytesToRead && i < buffers.size(); ++i) { + int start = 0; + int end = buffers.at(i).size(); + if (i == 0) + start = head; + if (i == tailBuffer) + end = tail; + const int len = qMin(ret.size()-readSoFar, end-start); + memcpy(ret.data()+readSoFar, buffers.at(i).constData()+start, len); + readSoFar += len; + } + Q_ASSERT(readSoFar == ret.size()); + return ret; + } + + inline int skip(int length) { + return read(0, length); + } + + inline int readLine(char *data, int maxLength) { + int index = indexOf('\n'); + if (index == -1) + return read(data, maxLength); + if (maxLength <= 0) + return -1; + + int readSoFar = 0; + while (readSoFar < index + 1 && readSoFar < maxLength - 1) { + int bytesToRead = qMin((index + 1) - readSoFar, nextDataBlockSize()); + bytesToRead = qMin(bytesToRead, (maxLength - 1) - readSoFar); + memcpy(data + readSoFar, readPointer(), bytesToRead); + readSoFar += bytesToRead; + free(bytesToRead); + } + + // Terminate it. + data[readSoFar] = '\0'; + return readSoFar; + } + + inline bool canReadLine() const { + return indexOf('\n') != -1; + } + +private: + QList<QByteArray> buffers; + int head, tail; + int tailBuffer; // always buffers.size() - 1 + int basicBlockSize; + int bufferSize; +}; + +QT_END_NAMESPACE + +#endif // QRINGBUFFER_P_H diff --git a/src/serialport/qt4support/qwineventnotifier_p.h b/src/serialport/qt4support/qwineventnotifier_p.h new file mode 100644 index 0000000..a87994d --- /dev/null +++ b/src/serialport/qt4support/qwineventnotifier_p.h @@ -0,0 +1,94 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this +** file. Please review the following information to ensure the GNU Lesser +** General Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QWINEVENTNOTIFIER_P_H +#define QWINEVENTNOTIFIER_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of other Qt classes. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "QtCore/qobject.h" +#include "QtCore/qt_windows.h" + +QT_BEGIN_NAMESPACE + +class Q_CORE_EXPORT QWinEventNotifier : public QObject +{ + Q_OBJECT + Q_DECLARE_PRIVATE(QObject) + +public: + explicit QWinEventNotifier(QObject *parent = 0); + explicit QWinEventNotifier(HANDLE hEvent, QObject *parent = 0); + ~QWinEventNotifier(); + + void setHandle(HANDLE hEvent); + HANDLE handle() const; + + bool isEnabled() const; + +public Q_SLOTS: + void setEnabled(bool enable); + +Q_SIGNALS: + void activated(HANDLE hEvent); + +protected: + bool event(QEvent * e); + +private: + Q_DISABLE_COPY(QWinEventNotifier) + + HANDLE handleToEvent; + bool enabled; +}; + +QT_END_NAMESPACE + +#endif // QWINEVENTNOTIFIER_P_H diff --git a/src/serialport/qt4support/serialport.prf b/src/serialport/qt4support/serialport.prf new file mode 100644 index 0000000..489acb5 --- /dev/null +++ b/src/serialport/qt4support/serialport.prf @@ -0,0 +1,29 @@ +!exists($$[QT_INSTALL_HEADERS]/QtAddOnSerialPort) { + INCLUDEPATH += $$SERIALPORT_BUILD_ROOT/include $$SERIALPORT_BUILD_ROOT/include/QtAddOnSerialPort + + SERIALPORT_BUILD_SUBDIR = src/serialport + win32 { + CONFIG(debug, debug|release) { + SERIALPORT_BUILD_SUBDIR = $$SERIALPORT_BUILD_SUBDIR/debug + } else { + SERIALPORT_BUILD_SUBDIR = $$SERIALPORT_BUILD_SUBDIR/release + } + } + + LIBS += -L$$SERIALPORT_BUILD_ROOT/$$SERIALPORT_BUILD_SUBDIR +} + +qtAddLibrary(SerialPort) + +mac { + LIBS -= -framework SerialPort$${QT_LIBINFIX} + + if(!debug_and_release|build_pass):CONFIG(debug, debug|release) { + LIBS += -lSerialPort$${QT_LIBINFIX}_debug + } else { + LIBS += -lSerialPort$${QT_LIBINFIX} + } +} + +INCLUDEPATH -= $$[QT_INSTALL_HEADERS]/SerialPort +INCLUDEPATH += $$[QT_INSTALL_HEADERS]/QtAddOnSerialPort diff --git a/src/serialport/serialport-global.h b/src/serialport/serialport-global.h new file mode 100644 index 0000000..d0f7680 --- /dev/null +++ b/src/serialport/serialport-global.h @@ -0,0 +1,71 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Denis Shienkov <scapig@yandex.ru> +** Contact: http://www.qt-project.org/ +** +** This file is part of the QtSerialPort module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this +** file. Please review the following information to ensure the GNU Lesser +** General Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef SERIALPORT_GLOBAL_H +#define SERIALPORT_GLOBAL_H + +#include "qglobal.h" + +#if defined(QT_SERIALPORT_LIB) +# define Q_SERIALPORT_EXPORT Q_DECL_EXPORT +#else +# define Q_SERIALPORT_EXPORT Q_DECL_IMPORT +#endif + +#if defined(QT_NAMESPACE) +# define QT_BEGIN_NAMESPACE_SERIALPORT namespace QT_NAMESPACE { namespace QtAddOn { namespace SerialPort { +# define QT_END_NAMESPACE_SERIALPORT } } } +# define QT_USE_NAMESPACE_SERIALPORT using namespace QT_NAMESPACE::QtAddOn::SerialPort; +# define QT_PREPEND_NAMESPACE_SERIALPORT(name) ::QT_NAMESPACE::QtAddOn::SerialPort::name +#else +# define QT_BEGIN_NAMESPACE_SERIALPORT namespace QtAddOn { namespace SerialPort { +# define QT_END_NAMESPACE_SERIALPORT } } +# define QT_USE_NAMESPACE_SERIALPORT using namespace QtAddOn::SerialPort; +# define QT_PREPEND_NAMESPACE_SERIALPORT(name) ::QtAddOn::SerialPort::name +#endif + +// a workaround for moc - if there is a header file that doesn't use serialport +// namespace, we still force moc to do "using namespace" but the namespace have to +// be defined, so let's define an empty namespace here +QT_BEGIN_NAMESPACE_SERIALPORT +QT_END_NAMESPACE_SERIALPORT + +#endif // SERIALPORT_GLOBAL_H diff --git a/src/serialport/serialport-lib.pri b/src/serialport/serialport-lib.pri new file mode 100644 index 0000000..3d946c1 --- /dev/null +++ b/src/serialport/serialport-lib.pri @@ -0,0 +1,80 @@ +INCLUDEPATH += $$PWD + +linux*:DEFINES += HAVE_LIBUDEV + +PUBLIC_HEADERS += \ + $$PWD/serialport-global.h \ + $$PWD/serialport.h \ + $$PWD/serialportinfo.h + +PRIVATE_HEADERS += \ + $$PWD/serialport_p.h \ + $$PWD/serialportinfo_p.h + +SOURCES += \ + $$PWD/serialport.cpp \ + $$PWD/serialportinfo.cpp + +win32 { + PRIVATE_HEADERS += \ + $$PWD/serialport_win_p.h + + SOURCES += \ + $$PWD/serialport_win.cpp \ + $$PWD/serialportinfo_win.cpp + + !wince*: { + LIBS += -lsetupapi -ladvapi32 + } else { + SOURCES += \ + $$PWD/serialport_wince.cpp \ + $$PWD/serialportinfo_wince.cpp + } +} + +symbian { + MMP_RULES += EXPORTUNFROZEN + #MMP_RULES += DEBUGGABLE_UDEBONLY + TARGET.UID3 = 0xE7E62DFD + TARGET.CAPABILITY = + TARGET.EPOCALLOWDLLDATA = 1 + addFiles.sources = SerialPort.dll + addFiles.path = !:/sys/bin + DEPLOYMENT += addFiles + + # FIXME !!! + #INCLUDEPATH += c:/Nokia/devices/Nokia_Symbian3_SDK_v1.0/epoc32/include/platform + INCLUDEPATH += c:/QtSDK/Symbian/SDKs/Symbian3Qt473/epoc32/include/platform + + PRIVATE_HEADERS += \ + $$PWD/serialport_symbian_p.h + + SOURCES += \ + $$PWD/serialport_symbian.cpp \ + $$PWD/serialportinfo_symbian.cpp + + LIBS += -leuser -lefsrv -lc32 +} + +unix:!symbian { + PRIVATE_HEADERS += \ + $$PWD/ttylocker_unix_p.h \ + $$PWD/serialport_unix_p.h + + SOURCES += \ + $$PWD/ttylocker_unix.cpp \ + $$PWD/serialport_unix.cpp \ + $$PWD/serialportinfo_unix.cpp + + macx { + SOURCES += $$PWD/serialportinfo_mac.cpp + + LIBS += -framework IOKit -framework CoreFoundation + } else { + linux*:contains( DEFINES, HAVE_LIBUDEV ) { + LIBS += -ludev + } + } +} + +HEADERS += $$PUBLIC_HEADERS $$PRIVATE_HEADERS diff --git a/src/serialport/serialport.cpp b/src/serialport/serialport.cpp new file mode 100644 index 0000000..178a835 --- /dev/null +++ b/src/serialport/serialport.cpp @@ -0,0 +1,1086 @@ +/**************************************************************************** +** +** Copyright (C) 2011-2012 Denis Shienkov <scapig2@yandex.ru> +** Copyright (C) 2011 Sergey Belyashov <Sergey.Belyashov@gmail.com> +** Copyright (C) 2012 Laszlo Papp <lpapp@kde.org> +** Copyright (C) 2012 Andre Hartmann <aha_1980@gmx.de> +** Contact: http://www.qt-project.org/ +** +** This file is part of the QtSerialPort module 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 "serialport.h" +#include "serialportinfo.h" + +#ifdef Q_OS_WIN +#include "serialport_win_p.h" +#elif defined (Q_OS_SYMBIAN) +#include "serialport_symbian_p.h" +#elif defined (Q_OS_UNIX) +#include "serialport_unix_p.h" +#else +#error Unsupported OS +#endif + +#ifndef SERIALPORT_BUFFERSIZE +# define SERIALPORT_BUFFERSIZE 16384 +#endif + +QT_BEGIN_NAMESPACE_SERIALPORT + +SerialPortPrivateData::SerialPortPrivateData(SerialPort *q) + : readBufferMaxSize(0) + , readBuffer(SERIALPORT_BUFFERSIZE) + , writeBuffer(SERIALPORT_BUFFERSIZE) + , portError(SerialPort::NoError) + , inputRate(0) + , outputRate(0) + , dataBits(SerialPort::UnknownDataBits) + , parity(SerialPort::UnknownParity) + , stopBits(SerialPort::UnknownStopBits) + , flow(SerialPort::UnknownFlowControl) + , restoreSettingsOnClose(true) + , q_ptr(q) +{ +} + +int SerialPortPrivateData::timeoutValue(int msecs, int elapsed) +{ + if (msecs == -1) + return msecs; + msecs -= elapsed; + return qMax(msecs, 0); +} + +/*! + \class SerialPort + + \brief The SerialPort class provides functions to access + serial ports. + + \reentrant + \ingroup serialport-main + \inmodule QtAddOnSerialPort + \since 5.0 + + This class resembles the functionality and behavior of the QAbstractSocket + class in many aspects, for instance the I/O operations, the implementation + of the wait methods, the internal architecture and so forth. Certain + SerialPort method implementations were taken directly from QAbstractSocket + with only minor changes. + + The features of the implementation and the conduct of the class are + listed below: + + \list + \o Provides only common functionality which includes + configuring, I/O data stream, get and set control signals of the + RS-232 lines. + \o Does not support for terminal features as echo, control CR/LF and so + forth. + \o Always works in binary mode. + \o Does not support the native ability for configuring timeouts + and delays while reading. + \o Does not provide tracking and notification when the state + of RS-232 lines was changed. + \endlist + + To get started with the SerialPort class, first create an object of + that. + + Then, call the setPort() method in order to assign the object with the name + of the desired serial port (which has to be present in the system). + The name has to follow a certain format, which is platform dependent. + + The helper class SerialPortInfo allows the enumeration of all the serial + ports in the system. This is useful to obtain the correct serial port name. + + The SerialPortInfo class can also be used as an input parameter for the + setPort() method (to retrieve the currently assigned name, use the + portName() method). + + After that, the serial port can be opened in read-only (r/o), write-only + (w/o) or read-write (r/w) mode using the open() method. + + Note: The serial port is always opened with exclusive access + (i.e. no other process or thread can access an already opened serial port). + + Having successfully opened, the SerialPort determines its current + configuration and initializes itself to that. To access the current + configuration use the rate(), dataBits(), parity(), stopBits(), and + flowControl() methods. + + If these settings are satisfying, the I/O operation can be proceed with. + Otherwise the port can be reconfigured to the desired setting using the + setRate(), setDataBits(), setParity(), setStopBits(), and setFlowControl() + methods. + + Read or write the data by calling read() or write(). Alternatively the + readLine() and readAll() convenience methods can also be invoked. The + SerialPort class also inherits the getChar(), putChar(), and ungetChar() + methods from the QIODevice class. Those methods work on single bytes. The + bytesWritten() signal is emitted when data has been written to the serial + port. Note that, Qt does not limit the write buffer size, which can be + monitored by listening to this signal. + + The readyRead() signal is emitted every time a new chunk of data + has arrived. The bytesAvailable() method then returns the number of bytes + that are available for reading. Typically, the readyRead() signal would be + connected to a slot and all data available could be read in there. + + If not all the data is read at once, the remaining data will + still be available later. Any new incoming data will be + appended to the SerialPort's internal read buffer. In order to limit the + size of the read buffer, call setReadBufferSize(). + + The status of the control lines is determined with the dtr(), rts(), and + lines() methods. To change the control line status, use the setDtr(), and + setRts() methods. + + To close the serial port, call the close() method. After all the pending data + has been written to the serial port, the SerialPort class actually closes + the descriptor. + + SerialPort provides a set of functions that suspend the calling + thread until certain signals are emitted. These functions can be + used to implement blocking serial ports: + + \list + \o waitForReadyRead() blocks until new data is available for + reading. + + \o waitForBytesWritten() blocks until one payload of data has been + written to the serial port. + \endlist + + See the following example: + + \code + int numRead = 0, numReadTotal = 0; + char buffer[50]; + + forever { + numRead = serial.read(buffer, 50); + + // Do whatever with the array + + numReadTotal += numRead; + if (numRead == 0 && !serial.waitForReadyRead()) + break; + } + \endcode + + If \l{QIODevice::}{waitForReadyRead()} returns false, the + connection has been closed or an error has occurred. + + Programming with a blocking serial port is radically different from + programming with a non-blocking serial port. A blocking serial port + does not require an event loop and typically leads to simpler code. + However, in a GUI application, blocking serial port should only be + used in non-GUI threads, to avoid freezing the user interface. + + See the \l examples/terminal and \l examples/blockingterminal + examples for an overview of both approaches. + + The use of blocking functions is discouraged together with signals. One of + the two possibilities should be used. + + The SerialPort class can be used with QTextStream and QDataStream's stream + operators (operator<<() and operator>>()). There is one issue to be aware + of, though: make sure that enough data is available before attempting to + read by using the operator>>() overloaded operator. + + \sa SerialPortInfo +*/ + +/*! + \enum SerialPort::Direction + + This enum describes the possible directions of the data transmission. + Note: This enumeration is used for setting the baud rate of the device + separately for each direction in case some operating systems (i.e. POSIX-like). + + \value Input Input direction. + \value Output Output direction. + \value AllDirections Simultaneously in two directions. +*/ + +/*! + \enum SerialPort::Rate + + This enum describes the baud rate which the communication device operates + with. Note: only the most common standard rates are listed in this enum. + + \value Rate1200 1200 baud. + \value Rate2400 2400 baud. + \value Rate4800 4800 baud. + \value Rate9600 9600 baud. + \value Rate19200 19200 baud. + \value Rate38400 38400 baud. + \value Rate57600 57600 baud. + \value Rate115200 115200 baud. + \value UnknownRate Unknown baud. + + \sa setRate(), rate() +*/ + +/*! + \enum SerialPort::DataBits + + This enum describes the number of data bits used. + + \value Data5 Five bits. + \value Data6 Six bits. + \value Data7 Seven bits + \value Data8 Eight bits. + \value UnknownDataBits Unknown number of bits. + + \sa setDataBits(), dataBits() +*/ + +/*! + \enum SerialPort::Parity + + This enum describes the parity scheme used. + + \Value NoParity No parity. + \value EvenParity Even parity. + \value OddParity Odd parity. + \value SpaceParity Space parity. + \value MarkParity Mark parity. + \value UnknownParity Unknown parity. + + \sa setParity(), parity() +*/ + +/*! + \enum SerialPort::StopBits + + This enum describes the number of stop bits used. + + \value OneStop 1 stop bit. + \value OneAndHalfStop 1.5 stop bits. + \value TwoStop 2 stop bits. + \value UnknownStopBits Unknown number of stop bit. + + \sa setStopBits(), stopBits() +*/ + +/*! + \enum SerialPort::FlowControl + + This enum describes the flow control used. + + \value NoFlowControl No flow control. + \value HardwareControl Hardware flow control (RTS/CTS). + \value SoftwareControl Software flow control (XON/XOFF). + \value UnknownFlowControl Unknown flow control. + + \sa setFlowControl(), flowControl() +*/ + +/*! + \enum SerialPort::Line + + This enum describes the possible RS-232 pinout signals. + + \value Le DSR (data set ready/line enable). + \value Dtr DTR (data terminal ready). + \value Rts RTS (request to send). + \value St Secondary TXD (transmit). + \value Sr Secondary RXD (receive). + \value Cts CTS (clear to send). + \value Dcd DCD (data carrier detect). + \value Ri RNG (ring). + \value Dsr DSR (data set ready). + + \sa lines(), setDtr(), setRts(), dtr(), rts() +*/ + +/*! + \enum SerialPort::DataErrorPolicy + + This enum describes the policies for the received symbols + while parity errors were detected. + + \value SkipPolicy Skips the bad character. + \value PassZeroPolicy Replaces bad character to zero. + \value IgnorePolicy Ignores the error for a bad character. + \value StopReceivingPolicy Stops data reception on error. + \value UnknownPolicy Unknown policy. + + \sa setDataErrorPolicy(), dataErrorPolicy() +*/ + +/*! + \enum SerialPort::PortError + + This enum describes the errors that may be returned by the error() + method. + + \value NoError No error occurred. + \value NoSuchDeviceError An error occurred while attempting to + open an non-existing device. + \value PermissionDeniedError An error occurred while attempting to + open an already opened device by another process or a user not + having enough permission and credentials to open. + \value DeviceAlreadyOpenedError An error occurred while attempting to + open an already opened device in this object. + \value DeviceIsNotOpenedError An error occurred while attempting to + control a device still closed. + \value ParityError Parity error detected by the hardware while reading data. + \value FramingError Framing error detected by the hardware while reading data. + \value BreakConditionError Break condition detected by the hardware on + the input line. + \value IoError An I/O error occurred while reading or writing the data. + \value UnsupportedPortOperationError The requested device operation is + not supported or prohibited by the running operating system. + \value UnknownPortError An unidentified error occurred. + + \sa error(), unsetError() +*/ + + + +/*! + Constructs a new serial port object with the given \a parent. +*/ +SerialPort::SerialPort(QObject *parent) + : QIODevice(parent) + , d_ptr(new SerialPortPrivate(this)) +{} + +/*! + Constructs a new serial port object with the given \a parent + to represent the serial port with the specified \a name. + + The name should have a specific format; see the setPort() method. +*/ +SerialPort::SerialPort(const QString &name, QObject *parent) + : QIODevice(parent) + , d_ptr(new SerialPortPrivate(this)) +{ + setPort(name); +} + +/*! + Constructs a new serial port object with the given \a parent + to represent the serial port with the specified a helper + class \a info. +*/ +SerialPort::SerialPort(const SerialPortInfo &info, QObject *parent) + : QIODevice(parent) + , d_ptr(new SerialPortPrivate(this)) +{ + setPort(info); +} + +/*! + Closes the serial port, if neccessary, and then destroys object. +*/ +SerialPort::~SerialPort() +{ + /**/ + close(); + delete d_ptr; +} + +/*! + Sets the \a name of the port. The name may be in any format; + either short, or also as system location (with all the prefixes and + postfixed). As a result, this name will be automatically written + and converted into an internal variable as system location. + + \sa portName(), SerialPortInfo +*/ +void SerialPort::setPort(const QString &name) +{ + Q_D(SerialPort); + d->systemLocation = SerialPortPrivate::portNameToSystemLocation(name); +} + +/*! + Sets the port stored in the serial port info instance \a info. + + \sa portName(), SerialPortInfo +*/ +void SerialPort::setPort(const SerialPortInfo &info) +{ + Q_D(SerialPort); + d->systemLocation = SerialPortPrivate::portNameToSystemLocation(info.systemLocation()); +} + +/*! + Returns the name set by setPort() or to the SerialPort constructors. + This name is short, i.e. it extract and convert out from the internal + variable system location of the device. Conversion algorithm is + platform specific: + \table + \header + \o Platform + \o Brief Description + \row + \o Windows + \o Removes the prefix "\\\\.\\" from the system location + and returns the remainder of the string. + \row + \o Windows CE + \o Removes the postfix ":" from the system location + and returns the remainder of the string. + \row + \o Symbian + \o Returns the system location as it is, + as it is equivalent to the port name. + \row + \o GNU/Linux + \o Removes the prefix "/dev/" from the system location + and returns the remainder of the string. + \row + \o Mac OSX + \o Removes the prefix "/dev/cu." and "/dev/tty." from the + system location and returns the remainder of the string. + \row + \o Other *nix + \o The same as for GNU/Linux. + \endtable + + \sa setPort(), SerialPortInfo::portName() +*/ +QString SerialPort::portName() const +{ + Q_D(const SerialPort); + return SerialPortPrivate::portNameFromSystemLocation(d->systemLocation); +} + +/*! \reimp + Opens the serial port using OpenMode \a mode, and then returns true if + successful; otherwise returns false with and sets an error code which can be + obtained by calling the error() method. + + \warning The \a mode has to be QIODevice::ReadOnly, QIODevice::WriteOnly, + or QIODevice::ReadWrite. This may also have additional flags, such as + QIODevice::Unbuffered. Other modes are unsupported. + + \sa QIODevice::OpenMode, setPort() +*/ +bool SerialPort::open(OpenMode mode) +{ + Q_D(SerialPort); + + if (isOpen()) { + d->portError = SerialPort::DeviceAlreadyOpenedError; + return false; + } + + // Define while not supported modes. + static const OpenMode unsupportedModes = Append | Truncate | Text | Unbuffered; + if ((mode & unsupportedModes) || mode == NotOpen) { + d->portError = SerialPort::UnsupportedPortOperationError; + return false; + } + + unsetError(); + if (d->open(mode)) { + QIODevice::open(mode); + return true; + } + return false; +} + +/*! \reimp + Calls SerialPort::flush() and closes the serial port. + Errors from flush are ignored. + + \sa QIODevice::close() +*/ +void SerialPort::close() +{ + Q_D(SerialPort); + if (!isOpen()) { + d->portError = SerialPort::DeviceIsNotOpenedError; + return; + } + + QIODevice::close(); + d->close(); +} + +/*! + Sets or clears the flag \a restore, which allows to restore the + previous settings while closing the serial port. If this flag + is true, the settings will be restored; otherwise not. + The default state of the SerialPort class is configured to restore the + settings. + + \sa restoreSettingsOnClose() +*/ +void SerialPort::setRestoreSettingsOnClose(bool restore) +{ + Q_D( SerialPort); + d->restoreSettingsOnClose = restore; +} + +/*! + Returns the current status of the restore flag settings on + closing the port. The default SerialPort is configured to + restore the settings. + + \sa setRestoreSettingsOnClose() +*/ +bool SerialPort::restoreSettingsOnClose() const +{ + Q_D(const SerialPort); + return d->restoreSettingsOnClose; +} + +/*! + Sets the desired data rate \a rate for a given direction \a dir. If + successful, returns true; otherwise returns false and sets an error code + which can be obtained by calling error(). To set the baud rate, use the + enumeration SerialPort::Rate or any positive qint32 value. + + \warning For OS Windows, Windows CE, Symbian supported only + AllDirections flag. + + \sa rate() +*/ +bool SerialPort::setRate(qint32 rate, Directions dir) +{ + Q_D(SerialPort); + if (d->setRate(rate, dir)) { + if (dir & SerialPort::Input) + d->inputRate = rate; + if (dir & SerialPort::Output) + d->outputRate = rate; + return true; + } + return false; +} + +/*! + Returns the current baud rate of the chosen direction \a dir. + + \warning Returns equal rate in any direction for Operating Systems as + Windows, Windows CE, and Symbian. + + \sa setRate() +*/ +qint32 SerialPort::rate(Directions dir) const +{ + Q_D(const SerialPort); + if (dir == SerialPort::AllDirections) + return d->inputRate == d->outputRate ? + d->inputRate : SerialPort::UnknownRate; + return dir & SerialPort::Input ? d->inputRate : d->outputRate; +} + +/*! + Sets the desired number of data bits \a dataBits in a frame. + If successful, returns true; otherwise returns false and sets an error + code which can be obtained by calling the error() method. + + \sa dataBits() +*/ +bool SerialPort::setDataBits(DataBits dataBits) +{ + Q_D(SerialPort); + if (d->setDataBits(dataBits)) { + d->dataBits = dataBits; + return true; + } + return false; +} + +/*! + Returns the current number of data bits in a frame. + + \sa setDataBits() +*/ +SerialPort::DataBits SerialPort::dataBits() const +{ + Q_D(const SerialPort); + return d->dataBits; +} + +/*! + Sets the desired parity \a parity checking mode. + If successful, returns true; otherwise returns false and sets an error + code which can be obtained by calling the error() method. + + \sa parity() +*/ +bool SerialPort::setParity(Parity parity) +{ + Q_D(SerialPort); + if (d->setParity(parity)) { + d->parity = parity; + return true; + } + return false; +} + +/*! + Returns the current parity checking mode. + + \sa setParity() +*/ +SerialPort::Parity SerialPort::parity() const +{ + Q_D(const SerialPort); + return d->parity; +} + +/*! + Sets the desired number of stop bits \a stopBits in a frame. If successful, + returns true; otherwise returns false and sets an error code which can be + obtained by calling the error() method. + + \sa stopBits() +*/ +bool SerialPort::setStopBits(StopBits stopBits) +{ + Q_D(SerialPort); + if (d->setStopBits(stopBits)) { + d->stopBits = stopBits; + return true; + } + return false; +} + +/*! + Returns the current number of stop bits. + + \sa setStopBits() +*/ +SerialPort::StopBits SerialPort::stopBits() const +{ + Q_D(const SerialPort); + return d->stopBits; +} + +/*! + Sets the desired number flow control mode \a flow. + If successful, returns true; otherwise returns false and sets an error + code which can be obtained by calling the error() method. + + \sa flowControl() +*/ +bool SerialPort::setFlowControl(FlowControl flow) +{ + Q_D(SerialPort); + if (d->setFlowControl(flow)) { + d->flow = flow; + return true; + } + return false; +} + +/*! + Returns the current flow control mode. + + \sa setFlowControl() +*/ +SerialPort::FlowControl SerialPort::flowControl() const +{ + Q_D(const SerialPort); + return d->flow; +} + +/*! + Returns the current state of the line signal DTR. + If the signal state high, the return true; otherwise returns false; + + \sa lines() +*/ +bool SerialPort::dtr() const +{ + Q_D(const SerialPort); + return d->lines() & SerialPort::Dtr; +} + +/*! + Returns the current state of the line signal RTS. + If the signal state high, the return true; otherwise returns false; + + \sa lines() +*/ +bool SerialPort::rts() const +{ + Q_D(const SerialPort); + return d->lines() & SerialPort::Rts; +} + +/*! + Returns the bitmap states of the line signals. + From this result, it is possible to allocate the state of the + desired signal by applying a mask "AND", where the mask is + the desired enumeration value from SerialPort::Lines. + + \sa dtr(), rts(), setDtr(), setRts() +*/ +SerialPort::Lines SerialPort::lines() const +{ + Q_D(const SerialPort); + return d->lines(); +} + +/*! + This function writes as much as possible from the internal write + buffer to the underlying serial port without blocking. If any data + was written, this function returns true; otherwise returns false. + + Call this function for sending the buffered data immediately to the serial + port. The number of bytes successfully written depends on the operating + system. In most cases, this function does not need to be called, because the + SerialPort class will start sending data automatically once control is + returned to the event loop. In the absence of an event loop, call + waitForBytesWritten() instead. + + \sa write(), waitForBytesWritten() +*/ +bool SerialPort::flush() +{ + Q_D(SerialPort); + return d->flush(); +} + +/*! + Discards all characters from the output or input buffer, depending on + a given direction \a dir. Including clear an internal class buffers and + the UART (driver) buffers. Also terminate pending read or write operations. + If successful, returns true; otherwise returns false. +*/ +bool SerialPort::clear(Directions dir) +{ + Q_D(SerialPort); + if (dir & Input) + d->readBuffer.clear(); + if (dir & Output) + d->writeBuffer.clear(); + return d->clear(dir); +} + +/*! \reimp + + Returns true if no more data is currently available for reading; otherwise + returns false. + + This function is most commonly used when reading data from the + serial port in a loop. For example: + + \code + // This slot is connected to SerialPort::readyRead() + void SerialPortClass::readyReadSlot() + { + while (!port.atEnd()) { + QByteArray data = port.read(100); + .... + } + } + \endcode + + \sa bytesAvailable(), readyRead() + */ +bool SerialPort::atEnd() const +{ + Q_D(const SerialPort); + return QIODevice::atEnd() && (!isOpen() || d->readBuffer.isEmpty()); +} + +/*! + Sets the error policy \a policy process received character in + the case of parity error detection. If successful, returns + true; otherwise returns false. The default policy set is IgnorePolicy. + + \sa dataErrorPolicy() +*/ +bool SerialPort::setDataErrorPolicy(DataErrorPolicy policy) +{ + Q_D(SerialPort); + const bool ret = d->policy == policy || d->setDataErrorPolicy(policy); + if (ret) + d->policy = policy; + return ret; +} + +/*! + Returns current error policy. + + \sa setDataErrorPolicy() +*/ +SerialPort::DataErrorPolicy SerialPort::dataErrorPolicy() const +{ + Q_D(const SerialPort); + return d->policy; +} + +/*! + Returns the serial port's error status. + + The I/O device status returns an error code. For example, if open() + returns false, or a read/write operation returns -1, this function can + be called to find out the reason why the operation failed. + + \sa unsetError() +*/ +SerialPort::PortError SerialPort::error() const +{ + Q_D(const SerialPort); + return d->portError; +} + +/*! + Clears the error code to SerialPort::NoError. + + \sa error() +*/ +void SerialPort::unsetError() +{ + Q_D(SerialPort); + d->portError = SerialPort::NoError; +} + +/*! + Returns the size of the internal read buffer. This limits the + amount of data that the client can receive before calling the read() + or readAll() methods. + + A read buffer size of 0 (the default) means that the buffer has + no size limit, ensuring that no data is lost. + + \sa setReadBufferSize(), read() +*/ +qint64 SerialPort::readBufferSize() const +{ + Q_D(const SerialPort); + return d->readBufferMaxSize; +} + +/*! + Sets the size of SerialPort's internal read buffer to be \a + size bytes. + + If the buffer size is limited to a certain size, SerialPort + will not buffer more than this size of data. Exceptionally, a buffer + size of 0 means that the read buffer is unlimited and all + incoming data is buffered. This is the default. + + This option is useful if the data is only read at certain points + in time (for instance in a real-time streaming application) or if the serial + port should be protected against receiving too much data, which may + eventually causes that the application runs out of memory. + + \sa readBufferSize(), read() +*/ +void SerialPort::setReadBufferSize(qint64 size) +{ + Q_D(SerialPort); + + if (d->readBufferMaxSize == size) + return; + d->readBufferMaxSize = size; +} + +/*! \reimp + Always returns true. The serial port is a sequential device. +*/ +bool SerialPort::isSequential() const +{ + return true; +} + +/*! \reimp + Returns the number of incoming bytes that are waiting to be read. + + \sa bytesToWrite(), read() +*/ +qint64 SerialPort::bytesAvailable() const +{ + Q_D(const SerialPort); + return d->readBuffer.size() + QIODevice::bytesAvailable(); +} + +/*! \reimp + Returns the number of bytes that are waiting to be written. The + bytes are written when control goes back to the event loop or + when flush() is called. + + \sa bytesAvailable(), flush() +*/ +qint64 SerialPort::bytesToWrite() const +{ + Q_D(const SerialPort); + return d->writeBuffer.size() + QIODevice::bytesToWrite(); +} + +/*! \reimp + Returns true if a line of data can be read from the serial port; + otherwise returns false. + + \sa readLine() +*/ +bool SerialPort::canReadLine() const +{ + Q_D(const SerialPort); + const bool hasLine = d->readBuffer.canReadLine(); + return hasLine || QIODevice::canReadLine(); +} + +/*! \reimp + This function blocks until new data is available for reading and the + \l{QIODevice::}{readyRead()} signal has been emitted. The function + will timeout after \a msecs milliseconds. + + The function returns true if the readyRead() signal is emitted and + there is new data available for reading; otherwise it returns false + (if an error occurred or the operation timed out). + + \sa waitForBytesWritten() +*/ +bool SerialPort::waitForReadyRead(int msecs) +{ + Q_D(SerialPort); + return d->waitForReadyRead(msecs); +} + +/*! \reimp +*/ +bool SerialPort::waitForBytesWritten(int msecs) +{ + Q_D(SerialPort); + return d->waitForBytesWritten(msecs); +} + +/*! + Sets the desired state of the line signal DTR, + depending on the flag \a set. If successful, returns true; + otherwise returns false. + + If the flag is true then the DTR signal is set to high; otherwise low. + + \sa lines(), dtr() +*/ +bool SerialPort::setDtr(bool set) +{ + Q_D(SerialPort); + return d->setDtr(set); +} + +/*! + Sets the desired state of the line signal RTS, + depending on the flag \a set. If successful, returns true; + otherwise returns false. + + If the flag is true then the RTS signal is set to high; otherwise low. + + \sa lines(), rts() +*/ +bool SerialPort::setRts(bool set) +{ + Q_D(SerialPort); + return d->setRts(set); +} + +/*! + Sends a continuous stream of zero bits during a specified period + of time \a duration in msec if the terminal is using asynchronous + serial data. If successful, returns true; otherwise returns false. + + If the duration is zero then zero bits are transmitted by at least + 0.25 seconds, but no more than 0.5 seconds. + + If the duration is non zero then zero bits are transmitted within a certain + period of time depending on the implementation. + + \sa setBreak(), clearBreak() +*/ +bool SerialPort::sendBreak(int duration) +{ + Q_D(SerialPort); + return d->sendBreak(duration); +} + +/*! + Controls the signal break, depending on the flag \a set. + If successful, returns true; otherwise returns false. + + If \a set is true then enables the break transmission; otherwise disables. + + \sa clearBreak(), sendBreak() +*/ +bool SerialPort::setBreak(bool set) +{ + Q_D(SerialPort); + return d->setBreak(set); +} + +/*! \reimp +*/ +qint64 SerialPort::readData(char *data, qint64 maxSize) +{ + Q_D(SerialPort); + return d->readFromBuffer(data, maxSize); +} + +/*! \reimp +*/ +qint64 SerialPort::readLineData(char *data, qint64 maxSize) +{ + return QIODevice::readLineData(data, maxSize); +} + +/*! \reimp +*/ +qint64 SerialPort::writeData(const char *data, qint64 maxSize) +{ + Q_D(SerialPort); + return d->writeToBuffer(data, maxSize); +} + +/*! + \fn bool SerialPort::clearBreak(bool clear) + + Controls the signal break, depending on the flag \a clear. + If successful, returns true; otherwise returns false. + + If clear is false then enables the break transmission; otherwise disables. + + \sa setBreak(), sendBreak() +*/ + +#include "moc_serialport.cpp" + +QT_END_NAMESPACE_SERIALPORT diff --git a/src/serialport/serialport.h b/src/serialport/serialport.h new file mode 100644 index 0000000..769ecfb --- /dev/null +++ b/src/serialport/serialport.h @@ -0,0 +1,242 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Denis Shienkov <scapig2@yandex.ru> +** Contact: http://www.qt-project.org/ +** +** This file is part of the QtSerialPort module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this +** file. Please review the following information to ensure the GNU Lesser +** General Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef SERIALPORT_H +#define SERIALPORT_H + +#include <QtCore/qiodevice.h> + +#include "serialport-global.h" + +QT_BEGIN_NAMESPACE_SERIALPORT + +class SerialPortInfo; +class SerialPortPrivate; + +class Q_SERIALPORT_EXPORT SerialPort : public QIODevice +{ + Q_OBJECT + + //Q_PROPERTY(qint32 rate READ rate WRITE setRate) + //Q_PROPERTY(DataBits dataBits READ dataBits WRITE setDataBits) + //Q_PROPERTY(Parity parity READ parity WRITE setParity) + //Q_PROPERTY(StopBits stopBits READ stopBits WRITE setStopBits) + //Q_PROPERTY(FlowControl flowControl READ flowControl WRITE setFlowControl) + //Q_PROPERTY(DataErrorPolicy dataErrorPolicy READ dataErrorPolicy WRITE setDataErrorPolicy) + //Q_PROPERTY(bool dtr READ dtr WRITE setDtr) + //Q_PROPERTY(bool rts READ rts WRITE setRts) + //Q_PROPERTY(PortError error READ error RESET unsetError) + //Q_PROPERTY(bool restoreSettingsOnClose READ restoreSettingsOnClose WRITE setRestoreSettingsOnClose) + + Q_ENUMS( Directions Rate DataBits Parity StopBits FlowControl Lines DataErrorPolicy PortError ) + +public: + + enum Direction { + Input = 1, + Output = 2, + AllDirections = Input | Output + }; + Q_DECLARE_FLAGS(Directions, Direction) + + enum Rate { + Rate1200 = 1200, + Rate2400 = 2400, + Rate4800 = 4800, + Rate9600 = 9600, + Rate19200 = 19200, + Rate38400 = 38400, + Rate57600 = 57600, + Rate115200 = 115200, + UnknownRate = -1 + }; + + enum DataBits { + Data5 = 5, + Data6 = 6, + Data7 = 7, + Data8 = 8, + UnknownDataBits = -1 + }; + + enum Parity { + NoParity = 0, + EvenParity = 2, + OddParity = 3, + SpaceParity = 4, + MarkParity = 5, + UnknownParity = -1 + }; + + enum StopBits { + OneStop = 1, + OneAndHalfStop = 3, + TwoStop = 2, + UnknownStopBits = -1 + }; + + enum FlowControl { + NoFlowControl, + HardwareControl, + SoftwareControl, + UnknownFlowControl = -1 + }; + + enum Line { + Le = 0x01, + Dtr = 0x02, + Rts = 0x04, + St = 0x08, + Sr = 0x10, + Cts = 0x20, + Dcd = 0x40, + Ri = 0x80, + Dsr = Le + }; + Q_DECLARE_FLAGS(Lines, Line) + + enum DataErrorPolicy { + SkipPolicy, + PassZeroPolicy, + IgnorePolicy, + StopReceivingPolicy, + UnknownPolicy = -1 + }; + + enum PortError { + NoError, + NoSuchDeviceError, + PermissionDeniedError, + DeviceAlreadyOpenedError, + DeviceIsNotOpenedError, + ParityError, + FramingError, + BreakConditionError, + IoError, + UnsupportedPortOperationError, + UnknownPortError + }; + + explicit SerialPort(QObject *parent = 0); + explicit SerialPort(const QString &name, QObject *parent = 0); + explicit SerialPort(const SerialPortInfo &info, QObject *parent = 0); + virtual ~SerialPort(); + + void setPort(const QString &port); + void setPort(const SerialPortInfo &info); + QString portName() const; + + virtual bool open(OpenMode mode); + virtual void close(); + + void setRestoreSettingsOnClose(bool restore); + bool restoreSettingsOnClose() const; + + bool setRate(qint32 rate, Directions dir = AllDirections); + qint32 rate(Directions dir = AllDirections) const; + + bool setDataBits(DataBits dataBits); + DataBits dataBits() const; + + bool setParity(Parity parity); + Parity parity() const; + + bool setStopBits(StopBits stopBits); + StopBits stopBits() const; + + bool setFlowControl(FlowControl flow); + FlowControl flowControl() const; + + bool dtr() const; + bool rts() const; + + Lines lines() const; + + bool flush(); + bool clear(Directions dir = AllDirections); + virtual bool atEnd() const; + + bool setDataErrorPolicy(DataErrorPolicy policy = IgnorePolicy); + DataErrorPolicy dataErrorPolicy() const; + + PortError error() const; + void unsetError(); + + qint64 readBufferSize() const; + void setReadBufferSize(qint64 size); + + virtual bool isSequential() const; + + virtual qint64 bytesAvailable() const; + virtual qint64 bytesToWrite() const; + virtual bool canReadLine() const; + + virtual bool waitForReadyRead(int msecs); + virtual bool waitForBytesWritten(int msecs); + +public Q_SLOTS: + bool setDtr(bool set); + bool setRts(bool set); + bool sendBreak(int duration = 0); + bool setBreak(bool set = true); + bool clearBreak(bool clear = true); + +protected: + virtual qint64 readData(char *data, qint64 maxSize); + virtual qint64 readLineData(char *data, qint64 maxSize); + virtual qint64 writeData(const char *data, qint64 maxSize); + +private: + SerialPortPrivate * const d_ptr; + + Q_DECLARE_PRIVATE(SerialPort) + Q_DISABLE_COPY(SerialPort) +}; + +inline bool SerialPort::clearBreak(bool clear) +{ return setBreak(!clear); } + +Q_DECLARE_OPERATORS_FOR_FLAGS(SerialPort::Directions) +Q_DECLARE_OPERATORS_FOR_FLAGS(SerialPort::Lines) + +QT_END_NAMESPACE_SERIALPORT + +#endif // SERIALPORT_H diff --git a/src/serialport/serialport.pro b/src/serialport/serialport.pro new file mode 100644 index 0000000..5fdec9f --- /dev/null +++ b/src/serialport/serialport.pro @@ -0,0 +1,18 @@ +QT = core +DEFINES += QT_SERIALPORT_LIB +VERSION = 1.0.0 + +include($$PWD/serialport-lib.pri) + +greaterThan(QT_MAJOR_VERSION, 4) { + load(qt_build_config) + QT += core-private + TARGET = QtAddOnSerialPort + load(qt_module) +} else { + TEMPLATE = lib + TARGET = $$qtLibraryTarget(SerialPort$$QT_LIBINFIX) + include($$PWD/qt4support/install-helper.pri) + CONFIG += module create_prl + mac:QMAKE_FRAMEWORK_BUNDLE_NAME = $$TARGET +} diff --git a/src/serialport/serialport_p.h b/src/serialport/serialport_p.h new file mode 100644 index 0000000..852e7f5 --- /dev/null +++ b/src/serialport/serialport_p.h @@ -0,0 +1,87 @@ +/**************************************************************************** +** +** Copyright (C) 2011-2012 Denis Shienkov <scapig2@yandex.ru> +** Copyright (C) 2011 Sergey Belyashov <Sergey.Belyashov@gmail.com> +** Copyright (C) 2012 Laszlo Papp <lpapp@kde.org> +** Contact: http://www.qt-project.org/ +** +** This file is part of the QtSerialPort module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this +** file. Please review the following information to ensure the GNU Lesser +** General Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef SERIALPORT_P_H +#define SERIALPORT_P_H + +#include "serialport.h" + +#if (QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)) +#include <private/qringbuffer_p.h> +#else +#include "qt4support/qringbuffer_p.h" +#endif + +QT_BEGIN_NAMESPACE_SERIALPORT + +class SerialPortPrivateData +{ + Q_DECLARE_PUBLIC(SerialPort) +public: + enum IoConstants { + ReadChunkSize = 512, + WriteChunkSize = 512 + }; + + SerialPortPrivateData(SerialPort *q); + int timeoutValue(int msecs, int elapsed); + + qint64 readBufferMaxSize; + QRingBuffer readBuffer; + QRingBuffer writeBuffer; + SerialPort::PortError portError; + QString systemLocation; + qint32 inputRate; + qint32 outputRate; + SerialPort::DataBits dataBits; + SerialPort::Parity parity; + SerialPort::StopBits stopBits; + SerialPort::FlowControl flow; + SerialPort::DataErrorPolicy policy; + bool restoreSettingsOnClose; + SerialPort * const q_ptr; +}; + +QT_END_NAMESPACE_SERIALPORT + +#endif // SERIALPORT_P_H diff --git a/src/serialport/serialport_symbian.cpp b/src/serialport/serialport_symbian.cpp new file mode 100644 index 0000000..c2542fd --- /dev/null +++ b/src/serialport/serialport_symbian.cpp @@ -0,0 +1,645 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Denis Shienkov <scapig@yandex.ru> +** Contact: http://www.qt-project.org/ +** +** This file is part of the QtSerialPort module 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 "serialport_symbian_p.h" + +#include <e32base.h> +//#include <e32test.h> +#include <f32file.h> + +#include <QtCore/qregexp.h> + +QT_BEGIN_NAMESPACE_SERIALPORT + +// Physical device driver. +#ifdef __WINS__ +_LIT(KPddName, "ECDRV"); +#else // defined (__EPOC32__) +_LIT(KPddName, "EUART"); +#endif + +// Logical device driver. +_LIT(KLddName,"ECOMM"); + +// Modules names. +_LIT(KRS232ModuleName, "ECUART"); +_LIT(KBluetoothModuleName, "BTCOMM"); +_LIT(KInfraRedModuleName, "IRCOMM"); +_LIT(KACMModuleName, "ECACM"); + +// Return false on error load. +static bool loadDevices() +{ + TInt r = KErrNone; +#ifdef __WINS__ + RFs fileServer; + r = User::LeaveIfError(fileServer.Connect()); + if (r != KErrNone) + return false; + fileServer.Close (); +#endif + + r = User::LoadPhysicalDevice(KPddName); + if (r != KErrNone && r != KErrAlreadyExists) + return false; //User::Leave(r); + + r = User::LoadLogicalDevice(KLddName); + if (r != KErrNone && r != KErrAlreadyExists) + return false; //User::Leave(r); + +#ifndef __WINS__ + r = StartC32(); + if (r != KErrNone && r != KErrAlreadyExists) + return false; //User::Leave(r); +#endif + + return true; +} + +SerialPortPrivate::SerialPortPrivate(SerialPort *q) + : SerialPortPrivateData(q) + , errnum(KErrNone) +{ +} + +bool SerialPortPrivate::open(QIODevice::OpenMode mode) +{ + // FIXME: Maybe need added check an ReadWrite open mode? + Q_UNUSED(mode) + + if (!loadDevices()) { + portError = SerialPort::UnknownPortError; + return false; + } + + RCommServ server; + errnum = server.Connect(); + if (errnum != KErrNone) { + portError = decodeSystemError(); + return false; + } + + if (systemLocation.contains("BTCOMM")) + errnum = server.LoadCommModule(KBluetoothModuleName); + else if (systemLocation.contains("IRCOMM")) + errnum = server.LoadCommModule(KInfraRedModuleName); + else if (systemLocation.contains("ACM")) + errnum = server.LoadCommModule(KACMModuleName); + else + errnum = server.LoadCommModule(KRS232ModuleName); + + if (errnum != KErrNone) { + portError = decodeSystemError(); + return false; + } + + // In Symbian OS port opening only in R/W mode? + TPtrC portName(static_cast<const TUint16*>(systemLocation.utf16()), systemLocation.length()); + errnum = descriptor.Open(server, portName, ECommExclusive); + + if (errnum != KErrNone) { + portError = decodeSystemError(); + return false; + } + + // Save current port settings. + errnum = descriptor.Config(restoredSettings); + if (errnum != KErrNone) { + portError = decodeSystemError(); + return false; + } + + detectDefaultSettings(); + return true; +} + +void SerialPortPrivate::close() +{ + if (restoreSettingsOnClose) + descriptor.SetConfig(restoredSettings); + descriptor.Close(); +} + +SerialPort::Lines SerialPortPrivate::lines() const +{ + SerialPort::Lines ret = 0; + + TUint signalMask = 0; + descriptor.Signals(signalMask); + + if (signalMask & KSignalCTS) + ret |= SerialPort::Cts; + if (signalMask & KSignalDSR) + ret |= SerialPort::Dsr; + if (signalMask & KSignalDCD) + ret |= SerialPort::Dcd; + if (signalMask & KSignalRNG) + ret |= SerialPort::Ri; + if (signalMask & KSignalRTS) + ret |= SerialPort::Rts; + if (signalMask & KSignalDTR) + ret |= SerialPort::Dtr; + + //if (signalMask & KSignalBreak) + // ret |= + return ret; +} + +bool SerialPortPrivate::setDtr(bool set) +{ + TInt r; + if (set) + r = descriptor.SetSignalsToMark(KSignalDTR); + else + r = descriptor.SetSignalsToSpace(KSignalDTR); + + return r == KErrNone; +} + +bool SerialPortPrivate::setRts(bool set) +{ + TInt r; + if (set) + r = descriptor.SetSignalsToMark(KSignalRTS); + else + r = descriptor.SetSignalsToSpace(KSignalRTS); + + return r == KErrNone; +} + +bool SerialPortPrivate::flush() +{ + // TODO: Implement me + return false; +} + +bool SerialPortPrivate::clear(SerialPort::Directions dir) +{ + TUint flags = 0; + if (dir & SerialPort::Input) + flags |= KCommResetRx; + if (dir & SerialPort::Output) + flags |= KCommResetTx; + TInt r = descriptor.ResetBuffers(flags); + return r == KErrNone; +} + +bool SerialPortPrivate::sendBreak(int duration) +{ + TRequestStatus status; + descriptor.Break(status, TTimeIntervalMicroSeconds32(duration * 1000)); + return false; +} + +bool SerialPortPrivate::setBreak(bool set) +{ + // TODO: Implement me + return false; +} + +qint64 SerialPortPrivate::bytesAvailable() const +{ + return descriptor.QueryReceiveBuffer(); +} + +qint64 SerialPortPrivate::bytesToWrite() const +{ + // TODO: Implement me + return 0; +} + +qint64 SerialPortPrivate::readFromBuffer(char *data, qint64 maxSize) +{ + // TODO: Implement me + return -1; +} + +qint64 SerialPortPrivate::writeToBuffer(const char *data, qint64 maxSize) +{ + // TODO: Implement me + return -1; +} + +bool SerialPortPrivate::waitForReadyRead(int msec) +{ + // TODO: Implement me + return false; +} + +bool SerialPortPrivate::waitForBytesWritten(int msec) +{ + // TODO: Implement me + return false; +} + +bool SerialPortPrivate::setRate(qint32 rate, SerialPort::Directions dir) +{ + if (dir != SerialPort::AllDirections) { + portError = SerialPort::UnsupportedPortOperationError; + return false; + } + + rate = settingFromRate(rate); + if (rate) + currentSettings().iRate = static_cast<TBps>(rate); + else { + portError = SerialPort::UnsupportedPortOperationError; + return false; + } + + return updateCommConfig(); +} + +bool SerialPortPrivate::setDataBits(SerialPort::DataBits dataBits) +{ + switch (dataBits) { + case SerialPort::Data5: + currentSettings().iDataBits = EData5; + break; + case SerialPort::Data6: + currentSettings().iDataBits = EData6; + break; + case SerialPort::Data7: + currentSettings().iDataBits = EData7; + break; + case SerialPort::Data8: + currentSettings().iDataBits = EData8; + break; + default: + currentSettings().iDataBits = EData8; + break; + } + + return updateCommConfig(); +} + +bool SerialPortPrivate::setParity(SerialPort::Parity parity) +{ + switch (parity) { + case SerialPort::NoParity: + currentSettings().iParity = EParityNone; + break; + case SerialPort::EvenParity: + currentSettings().iParity = EParityEven; + break; + case SerialPort::OddParity: + currentSettings().iParity = EParityOdd; + break; + case SerialPort::MarkParity: + currentSettings().iParity = EParityMark; + break; + case SerialPort::SpaceParity: + currentSettings().iParity = EParitySpace; + break; + default: + currentSettings().iParity = EParityNone; + break; + } + + return updateCommConfig(); +} + +bool SerialPortPrivate::setStopBits(SerialPort::StopBits stopBits) +{ + switch (stopBits) { + case SerialPort::OneStop: + currentSettings().iStopBits = EStop1; + break; + case SerialPort::TwoStop: + currentSettings().iStopBits = EStop2; + break; + default: + currentSettings().iStopBits = EStop1; + break; + } + + return updateCommConfig(); +} + +bool SerialPortPrivate::setFlowControl(SerialPort::FlowControl flow) +{ + switch (flow) { + case SerialPort::NoFlowControl: + currentSettings().iHandshake = KConfigFailDSR; + break; + case SerialPort::HardwareControl: + currentSettings().iHandshake = KConfigObeyCTS | KConfigFreeRTS; + break; + case SerialPort::SoftwareControl: + currentSettings().iHandshake = KConfigObeyXoff | KConfigSendXoff; + break; + default: + currentSettings().iHandshake = KConfigFailDSR; + break; + } + + return updateCommConfig(); +} + +bool SerialPortPrivate::setDataErrorPolicy(SerialPort::DataErrorPolicy policy) +{ + // TODO: Implement me + return false; +} + +bool SerialPortPrivate::notifyRead() +{ + // TODO: Implement me + return false; +} + +bool SerialPortPrivate::notifyWrite() +{ + // TODO: Implement me + return false; +} + +bool SerialPortPrivate::updateCommConfig() +{ + if (descriptor.SetConfig(currentSettings) != KErrNone) { + portError = SerialPort::UnsupportedPortOperationError; + return false; + } + return true; +} + +void SerialPortPrivate::detectDefaultSettings() +{ + // Detect rate. + inputRate = rateFromSetting(currentSettings().iRate); + outputRate = inputRate; + + // Detect databits. + switch (currentSettings().iDataBits) { + case EData5: + dataBits = SerialPort::Data5; + break; + case EData6: + dataBits = SerialPort::Data6; + break; + case EData7: + dataBits = SerialPort::Data7; + break; + case EData8: + dataBits = SerialPort::Data8; + break; + default: + dataBits = SerialPort::UnknownDataBits; + break; + } + + // Detect parity. + switch (currentSettings().iParity) { + case EParityNone: + parity = SerialPort::NoParity; + break; + case EParityEven: + parity = SerialPort::EvenParity; + break; + case EParityOdd: + parity = SerialPort::OddParity; + break; + case EParityMark: + parity = SerialPort::MarkParity; + break; + case EParitySpace: + parity = SerialPort::SpaceParity; + break; + default: + parity = SerialPort::UnknownParity; + break; + } + + // Detect stopbits. + switch (currentSettings().iStopBits) { + case EStop1: + stopBits = SerialPort::OneStop; + break; + case EStop2: + stopBits = SerialPort::TwoStop; + break; + default: + stopBits = SerialPort::UnknownStopBits; + break; + } + + // Detect flow control. + if ((currentSettings().iHandshake & (KConfigObeyXoff | KConfigSendXoff)) + == (KConfigObeyXoff | KConfigSendXoff)) + flow = SerialPort::SoftwareControl; + else if ((currentSettings().iHandshake & (KConfigObeyCTS | KConfigFreeRTS)) + == (KConfigObeyCTS | KConfigFreeRTS)) + flow = SerialPort::HardwareControl; + else if (currentSettings().iHandshake & KConfigFailDSR) + flow = SerialPort::NoFlowControl; + else + flow = SerialPort::UnknownFlowControl; +} + +SerialPort::PortError SerialPortPrivate::decodeSystemError() const +{ + SerialPort::PortError error; + switch (errnum) { + case KErrPermissionDenied: + error = SerialPort::NoSuchDeviceError; + break; + case KErrLocked: + error = SerialPort::PermissionDeniedError; + break; + case KErrAccessDenied: + error = SerialPort::PermissionDeniedError; + break; + default: + error = SerialPort::UnknownPortError; + break; + } + return error; +} + +bool SerialPortPrivate::waitForReadOrWrite(bool *selectForRead, bool *selectForWrite, + bool checkRead, bool checkWrite, + int msecs, bool *timedOut) +{ + + // FIXME: I'm not sure in implementation this method. + // Someone needs to check and correct. + + TRequestStatus timerStatus; + TRequestStatus readStatus; + TRequestStatus writeStatus; + + if (msecs > 0) { + if (!selectTimer.Handle()) { + if (selectTimer.CreateLocal() != KErrNone) + return false; + } + selectTimer.HighRes(timerStatus, msecs * 1000); + } + + if (checkRead) + descriptor.NotifyDataAvailable(readStatus); + + if (checkWrite) + descriptor.NotifyOutputEmpty(writeStatus); + + enum { STATUSES_COUNT = 3 }; + TRequestStatus *statuses[STATUSES_COUNT]; + TInt num = 0; + statuses[num++] = &timerStatus; + statuses[num++] = &readStatus; + statuses[num++] = &writeStatus; + + User::WaitForNRequest(statuses, num); + + bool result = false; + + // By timeout? + if (timerStatus != KRequestPending) { + Q_ASSERT(selectForRead); + *selectForRead = false; + Q_ASSERT(selectForWrite); + *selectForWrite = false; + } else { + selectTimer.Cancel(); + User::WaitForRequest(timerStatus); + + // By read? + if (readStatus != KRequestPending) { + Q_ASSERT(selectForRead); + *selectForRead = true; + } + + // By write? + if (writeStatus != KRequestPending) { + Q_ASSERT(selectForWrite); + *selectForWrite = true; + } + + if (checkRead) + descriptor.NotifyDataAvailableCancel(); + if (checkWrite) + descriptor.NotifyOutputEmptyCancel(); + + result = true; + } + return result; +} + +QString SerialPortPrivate::portNameToSystemLocation(const QString &port) +{ + // Port name is equval to port systemLocation. + return port; +} + +QString SerialPortPrivate::portNameFromSystemLocation(const QString &location) +{ + // Port name is equval to port systemLocation. + return location; +} + +struct RatePair +{ + qint32 rate; // The numerical value of baud rate. + qint32 setting; // The OS-specific code of baud rate. + bool operator<(const RatePair &other) const { return rate < other.rate; } + bool operator==(const RatePair &other) const { return setting == other.setting; } +}; + +// This table contains correspondences standard pairs values of +// baud rates that are defined in files +// - d32comm.h for Symbian^3 +// - d32public.h for Symbian SR1 +static const RatePair standardRatesTable[] = +{ + { 50, EBps50 }, + { 75, EBps75 }, + { 110, EBps110}, + { 134, EBps134 }, + { 150, EBps150 }, + { 300, EBps300 }, + { 600, EBps600 }, + { 1200, EBps1200 }, + { 1800, EBps1800 }, + { 2000, EBps2000 }, + { 2400, EBps2400 }, + { 3600, EBps3600 }, + { 4800, EBps4800 }, + { 7200, EBps7200 }, + { 9600, EBps9600 }, + { 19200, EBps19200 }, + { 38400, EBps38400 }, + { 57600, EBps57600 }, + { 115200, EBps115200 }, + { 230400, EBps230400 }, + { 460800, EBps460800 }, + { 576000, EBps576000 }, + { 921600, EBps921600 }, + { 1152000, EBps1152000 }, + //{ 1843200, EBps1843200 }, only for Symbian SR1 + { 4000000, EBps4000000 } +}; + +static const RatePair *standardRatesTable_end = + standardRatesTable + sizeof(standardRatesTable)/sizeof(*standardRatesTable); + +qint32 SerialPortPrivate::rateFromSetting(qint32 setting) +{ + const RatePair rp = { 0, setting }; + const RatePair *ret = qFind(standardRatesTable, standardRatesTable_end, rp); + return ret != standardRatesTable_end ? ret->rate : 0; +} + +qint32 SerialPortPrivate::settingFromRate(qint32 rate) +{ + const RatePair rp = { rate, 0 }; + const RatePair *ret = qBinaryFind(standardRatesTable, standardRatesTable_end, rp); + return ret != standardRatesTable_end ? ret->setting : 0; +} + +QList<qint32> SerialPortPrivate::standardRates() +{ + QList<qint32> ret; + for (const RatePair *it = standardRatesTable; it != standardRatesTable_end; ++it) + ret.append(it->rate); + return ret; +} + +QT_END_NAMESPACE_SERIALPORT diff --git a/src/serialport/serialport_symbian_p.h b/src/serialport/serialport_symbian_p.h new file mode 100644 index 0000000..647585c --- /dev/null +++ b/src/serialport/serialport_symbian_p.h @@ -0,0 +1,116 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Denis Shienkov <scapig2@yandex.ru> +** Contact: http://www.qt-project.org/ +** +** This file is part of the QtSerialPort module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this +** file. Please review the following information to ensure the GNU Lesser +** General Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef SERIALPORT_SYMBIAN_P_H +#define SERIALPORT_SYMBIAN_P_H + +#include "serialport_p.h" + +#include <c32comm.h> + +QT_BEGIN_NAMESPACE_SERIALPORT + +class SerialPortPrivate : public SerialPortPrivateData +{ +public: + SerialPortPrivate(SerialPort *q); + + bool open(QIODevice::OpenMode mode); + void close(); + + SerialPort::Lines lines() const; + + bool setDtr(bool set); + bool setRts(bool set); + + bool flush(); + bool clear(SerialPort::Directions dir); + + bool sendBreak(int duration); + bool setBreak(bool set); + + qint64 bytesAvailable() const; + qint64 bytesToWrite() const; + + qint64 readFromBuffer(char *data, qint64 maxSize); + qint64 writeToBuffer(const char *data, qint64 maxSize); + + bool waitForReadyRead(int msec); + bool waitForBytesWritten(int msec); + + bool setRate(qint32 rate, SerialPort::Directions dir); + bool setDataBits(SerialPort::DataBits dataBits); + bool setParity(SerialPort::Parity parity); + bool setStopBits(SerialPort::StopBits stopBits); + bool setFlowControl(SerialPort::FlowControl flowControl); + bool setDataErrorPolicy(SerialPort::DataErrorPolicy policy); + + bool notifyRead(); + bool notifyWrite(); + + static QString portNameToSystemLocation(const QString &port); + static QString portNameFromSystemLocation(const QString &location); + + static qint32 rateFromSetting(qint32 setting); + static qint32 settingFromRate(qint32 rate); + + static QList<qint32> standardRates(); + + TCommConfig currentSettings; + TCommConfig restoredSettings; + RComm descriptor; + mutable RTimer selectTimer; + TInt errnum; + +private: + bool updateCommConfig(); + + void detectDefaultSettings(); + SerialPort::PortError decodeSystemError() const; + + bool waitForReadOrWrite(bool *selectForRead, bool *selectForWrite, + bool checkRead, bool checkWrite, + int msecs, bool *timedOut); +}; + +QT_END_NAMESPACE_SERIALPORT + +#endif // SERIALPORT_SYMBIAN_P_H diff --git a/src/serialport/serialport_unix.cpp b/src/serialport/serialport_unix.cpp new file mode 100644 index 0000000..4bb314b --- /dev/null +++ b/src/serialport/serialport_unix.cpp @@ -0,0 +1,1327 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Denis Shienkov <scapig@yandex.ru> +** Copyright (C) 2012 Laszlo Papp <lpapp@kde.org> +** Copyright (C) 2012 Andre Hartmann <aha_1980@gmx.de> +** Contact: http://www.qt-project.org/ +** +** This file is part of the QtSerialPort module 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 "serialport_unix_p.h" +#include "ttylocker_unix_p.h" + +#include <errno.h> +#include <sys/time.h> +#include <sys/ioctl.h> +#include <fcntl.h> +#include <unistd.h> + +#ifdef Q_OS_MAC +#if defined (MAC_OS_X_VERSION_10_4) && (MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_4) +#include <IOKit/serial/ioss.h> +#endif +#endif + +#include <QtCore/qelapsedtimer.h> + +#include <QtCore/qsocketnotifier.h> + +QT_BEGIN_NAMESPACE_SERIALPORT + +class ReadNotifier : public QSocketNotifier +{ +public: + ReadNotifier(SerialPortPrivate *d, QObject *parent) + : QSocketNotifier(d->descriptor, QSocketNotifier::Read, parent) + , dptr(d) + {} + +protected: + virtual bool event(QEvent *e) { + bool ret = QSocketNotifier::event(e); + if (ret) + dptr->readNotification(); + return ret; + } + +private: + SerialPortPrivate *dptr; +}; + +class WriteNotifier : public QSocketNotifier +{ +public: + WriteNotifier(SerialPortPrivate *d, QObject *parent) + : QSocketNotifier(d->descriptor, QSocketNotifier::Write, parent) + , dptr(d) + {} + +protected: + virtual bool event(QEvent *e) { + bool ret = QSocketNotifier::event(e); + if (ret) + dptr->writeNotification(SerialPortPrivateData::WriteChunkSize); + return ret; + } + +private: + SerialPortPrivate *dptr; +}; + +class ExceptionNotifier : public QSocketNotifier +{ +public: + ExceptionNotifier(SerialPortPrivate *d, QObject *parent) + : QSocketNotifier(d->descriptor, QSocketNotifier::Exception, parent) + , dptr(d) + {} + +protected: + virtual bool event(QEvent *e) { + bool ret = QSocketNotifier::event(e); + if (ret) + dptr->exceptionNotification(); + return ret; + } + +private: + SerialPortPrivate *dptr; +}; + +SerialPortPrivate::SerialPortPrivate(SerialPort *q) + : SerialPortPrivateData(q) + , descriptor(-1) + , isCustomRateSupported(false) + , readNotifier(0) + , writeNotifier(0) + , exceptionNotifier(0) + , readPortNotifierCalled(false) + , readPortNotifierState(false) + , readPortNotifierStateSet(false) + , emittedReadyRead(false) + , emittedBytesWritten(false) +{ +} + +bool SerialPortPrivate::open(QIODevice::OpenMode mode) +{ + QByteArray portName = portNameFromSystemLocation(systemLocation).toLocal8Bit(); + const char *ptr = portName.constData(); + + bool byCurrPid = false; + if (TtyLocker::isLocked(ptr, &byCurrPid)) { + portError = SerialPort::PermissionDeniedError; + return false; + } + + int flags = O_NOCTTY | O_NONBLOCK; + + switch (mode & QIODevice::ReadWrite) { + case QIODevice::WriteOnly: + flags |= O_WRONLY; + break; + case QIODevice::ReadWrite: + flags |= O_RDWR; + break; + default: + flags |= O_RDONLY; + break; + } + + descriptor = ::open(systemLocation.toLocal8Bit().constData(), flags); + + if (descriptor == -1) { + portError = decodeSystemError(); + return false; + } + + ::fcntl(descriptor, F_SETFL, FNDELAY); + + TtyLocker::lock(ptr); + if (!TtyLocker::isLocked(ptr, &byCurrPid)) { + portError = SerialPort::PermissionDeniedError; + return false; + } + +#ifdef TIOCEXCL + ::ioctl(descriptor, TIOCEXCL); +#endif + + if (::tcgetattr(descriptor, &restoredTermios) == -1) { + portError = decodeSystemError(); + return false; + } + + ::memset(¤tTermios, 0, sizeof(currentTermios)); + ::cfmakeraw(¤tTermios); + currentTermios.c_cflag |= CLOCAL; + currentTermios.c_cc[VTIME] = 0; + currentTermios.c_cc[VMIN] = 0; + + if (mode & QIODevice::ReadOnly) + currentTermios.c_cflag |= CREAD; + + if (!updateTermios()) + return false; + + setExceptionNotificationEnabled(true); + + if ((flags & O_WRONLY) == 0) + setReadNotificationEnabled(true); + + detectDefaultSettings(); + return true; +} + +void SerialPortPrivate::close() +{ + if (restoreSettingsOnClose) { + ::tcsetattr(descriptor, TCSANOW, &restoredTermios); +#ifdef Q_OS_LINUX + if (isCustomRateSupported) + ::ioctl(descriptor, TIOCSSERIAL, &restoredSerialInfo); +#endif + } + +#ifdef TIOCNXCL + ::ioctl(descriptor, TIOCNXCL); +#endif + + if (readNotifier) { + readNotifier->setEnabled(false); + readNotifier->deleteLater(); + readNotifier = 0; + } + + if (writeNotifier) { + writeNotifier->setEnabled(false); + writeNotifier->deleteLater(); + writeNotifier = 0; + } + + if (exceptionNotifier) { + exceptionNotifier->setEnabled(false); + exceptionNotifier->deleteLater(); + exceptionNotifier = 0; + } + + ::close(descriptor); + + QByteArray portName = portNameFromSystemLocation(systemLocation).toLocal8Bit(); + const char *ptr = portName.constData(); + + bool byCurrPid = false; + if (TtyLocker::isLocked(ptr, &byCurrPid) && byCurrPid) + TtyLocker::unlock(ptr); + + descriptor = -1; + isCustomRateSupported = false; +} + +SerialPort::Lines SerialPortPrivate::lines() const +{ + int arg = 0; + SerialPort::Lines ret = 0; + + if (::ioctl(descriptor, TIOCMGET, &arg) == -1) + return ret; + +#ifdef TIOCM_LE + if (arg & TIOCM_LE) + ret |= SerialPort::Le; +#endif +#ifdef TIOCM_DTR + if (arg & TIOCM_DTR) + ret |= SerialPort::Dtr; +#endif +#ifdef TIOCM_RTS + if (arg & TIOCM_RTS) + ret |= SerialPort::Rts; +#endif +#ifdef TIOCM_ST + if (arg & TIOCM_ST) + ret |= SerialPort::St; +#endif +#ifdef TIOCM_SR + if (arg & TIOCM_SR) + ret |= SerialPort::Sr; +#endif +#ifdef TIOCM_CTS + if (arg & TIOCM_CTS) + ret |= SerialPort::Cts; +#endif +#ifdef TIOCM_CAR + if (arg & TIOCM_CAR) + ret |= SerialPort::Dcd; +#elif defined TIOCM_CD + if (arg & TIOCM_CD) + ret |= SerialPort::Dcd; +#endif +#ifdef TIOCM_RNG + if (arg & TIOCM_RNG) + ret |= SerialPort::Ri; +#elif defined TIOCM_RI + if (arg & TIOCM_RI) + ret |= SerialPort::Ri; +#endif +#ifdef TIOCM_DSR + if (arg & TIOCM_DSR) + ret |= SerialPort::Dsr; +#endif + + return ret; +} + +bool SerialPortPrivate::setDtr(bool set) +{ + int status = TIOCM_DTR; + return ::ioctl(descriptor, set ? TIOCMBIS : TIOCMBIC, &status) != -1; +} + +bool SerialPortPrivate::setRts(bool set) +{ + int status = TIOCM_RTS; + return ::ioctl(descriptor, set ? TIOCMBIS : TIOCMBIC, &status) != -1; +} + +bool SerialPortPrivate::flush() +{ + return writeNotification() && (::tcdrain(descriptor) != -1); +} + +bool SerialPortPrivate::clear(SerialPort::Directions dir) +{ + return ::tcflush(descriptor, (dir == SerialPort::AllDirections) + ? TCIOFLUSH : (dir & SerialPort::Input) ? TCIFLUSH : TCOFLUSH) != -1; +} + +bool SerialPortPrivate::sendBreak(int duration) +{ + return ::tcsendbreak(descriptor, duration) != -1; +} + +bool SerialPortPrivate::setBreak(bool set) +{ + return ::ioctl(descriptor, set ? TIOCSBRK : TIOCCBRK) != -1; +} + +qint64 SerialPortPrivate::bytesAvailable() const +{ + int nbytes = 0; +#ifdef TIOCINQ + if (::ioctl(descriptor, TIOCINQ, &nbytes) == -1) + return -1; +#endif + return nbytes; +} + +qint64 SerialPortPrivate::bytesToWrite() const +{ + int nbytes = 0; +#ifdef TIOCOUTQ + if (::ioctl(descriptor, TIOCOUTQ, &nbytes) == -1) + return -1; +#endif + return nbytes; +} + +qint64 SerialPortPrivate::readFromBuffer(char *data, qint64 maxSize) +{ + if (readBuffer.isEmpty()) + return 0; + + if (maxSize == 1) { + *data = readBuffer.getChar(); + if (readBuffer.isEmpty()) + setReadNotificationEnabled(true); + return 1; + } + + const qint64 bytesToRead = qMin(qint64(readBuffer.size()), maxSize); + qint64 readSoFar = 0; + while (readSoFar < bytesToRead) { + const char *ptr = readBuffer.readPointer(); + const int bytesToReadFromThisBlock = qMin(int(bytesToRead - readSoFar), + readBuffer.nextDataBlockSize()); + ::memcpy(data + readSoFar, ptr, bytesToReadFromThisBlock); + readSoFar += bytesToReadFromThisBlock; + readBuffer.free(bytesToReadFromThisBlock); + } + + if (!isReadNotificationEnabled()) + setReadNotificationEnabled(true); + + if (readSoFar > 0) { + if (readBuffer.isEmpty()) + setReadNotificationEnabled(true); + return readSoFar; + } + + return readSoFar; +} + +qint64 SerialPortPrivate::writeToBuffer(const char *data, qint64 maxSize) +{ + char *ptr = writeBuffer.reserve(maxSize); + if (maxSize == 1) + *ptr = *data; + else + ::memcpy(ptr, data, maxSize); + + const qint64 written = maxSize; + + if (!writeBuffer.isEmpty() && !isWriteNotificationEnabled()) + setWriteNotificationEnabled(true); + + return written; +} + +bool SerialPortPrivate::waitForReadyRead(int msecs) +{ + QElapsedTimer stopWatch; + + stopWatch.start(); + + do { + bool readyToRead = false; + bool readyToWrite = false; + bool timedOut = false; + if (!waitForReadOrWrite(&readyToRead, &readyToWrite, true, !writeBuffer.isEmpty(), + timeoutValue(msecs, stopWatch.elapsed()), &timedOut)) { + // TODO: set error ? + return false; + } + + if (readyToRead) { + if (readNotification()) + return true; + } + + if (readyToWrite) + writeNotification(WriteChunkSize); + + } while (msecs == -1 || timeoutValue(msecs, stopWatch.elapsed()) > 0); + return false; +} + +bool SerialPortPrivate::waitForBytesWritten(int msecs) +{ + if (writeBuffer.isEmpty()) + return false; + + QElapsedTimer stopWatch; + + stopWatch.start(); + + forever { + bool readyToRead = false; + bool readyToWrite = false; + bool timedOut = false; + if (!waitForReadOrWrite(&readyToRead, &readyToWrite, true, !writeBuffer.isEmpty(), + timeoutValue(msecs, stopWatch.elapsed()), &timedOut)) { + // TODO: set error ? + return false; + } + + if (readyToRead && !readNotification()) + return false; + + if (readyToWrite && writeNotification(WriteChunkSize)) + return true; + } + return false; +} + +bool SerialPortPrivate::setRate(qint32 rate, SerialPort::Directions dir) +{ + bool ret = rate > 0; + + // prepare section + + if (ret) { + const qint32 unixRate = SerialPortPrivate::settingFromRate(rate); + if (unixRate > 0) { + // try prepate to set standard baud rate +#ifdef Q_OS_LINUX + // prepare to forcefully reset the custom mode + if (isCustomRateSupported) { + //currentSerialInfo.flags |= ASYNC_SPD_MASK; + currentSerialInfo.flags &= ~(ASYNC_SPD_CUST /* | ASYNC_LOW_LATENCY*/); + currentSerialInfo.custom_divisor = 0; + } +#endif + // prepare to set standard rate + ret = !(((dir & SerialPort::Input) && ::cfsetispeed(¤tTermios, unixRate) < 0) + || ((dir & SerialPort::Output) && ::cfsetospeed(¤tTermios, unixRate) < 0)); + } else { + // try prepate to set custom baud rate +#ifdef Q_OS_LINUX + // prepare to forcefully set the custom mode + if (isCustomRateSupported) { + currentSerialInfo.flags &= ~ASYNC_SPD_MASK; + currentSerialInfo.flags |= (ASYNC_SPD_CUST /* | ASYNC_LOW_LATENCY*/); + currentSerialInfo.custom_divisor = currentSerialInfo.baud_base / rate; + if (currentSerialInfo.custom_divisor == 0) + currentSerialInfo.custom_divisor = 1; + // for custom mode needed prepare to set B38400 rate + ret = (::cfsetspeed(¤tTermios, B38400) != -1); + } else { + ret = false; + } +#elif defined(Q_OS_MAC) + +# if defined (MAC_OS_X_VERSION_10_4) && (MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_4) + // Starting with Tiger, the IOSSIOSPEED ioctl can be used to set arbitrary baud rates + // other than those specified by POSIX. The driver for the underlying serial hardware + // ultimately determines which baud rates can be used. This ioctl sets both the input + // and output speed. + ret = ::ioctl(descriptor, IOSSIOSPEED, &rate) != -1; +# else + // others MacOSX version, can't prepare to set custom rate + ret = false; +# endif + +#else + // others *nix OS, can't prepare to set custom rate + ret = false; +#endif + } + } + + // finally section + +#ifdef Q_OS_LINUX + if (ret && isCustomRateSupported) // finally, set or reset the custom mode + ret = ::ioctl(descriptor, TIOCSSERIAL, ¤tSerialInfo) != -1; +#endif + + if (ret) // finally, set rate + ret = updateTermios(); + else + portError = decodeSystemError(); + return ret; +} + +bool SerialPortPrivate::setDataBits(SerialPort::DataBits dataBits) +{ + currentTermios.c_cflag &= ~CSIZE; + switch (dataBits) { + case SerialPort::Data5: + currentTermios.c_cflag |= CS5; + break; + case SerialPort::Data6: + currentTermios.c_cflag |= CS6; + break; + case SerialPort::Data7: + currentTermios.c_cflag |= CS7; + break; + case SerialPort::Data8: + currentTermios.c_cflag |= CS8; + break; + default: + currentTermios.c_cflag |= CS8; + break; + } + return updateTermios(); +} + +bool SerialPortPrivate::setParity(SerialPort::Parity parity) +{ + currentTermios.c_iflag &= ~(PARMRK | INPCK); + currentTermios.c_iflag |= IGNPAR; + + switch (parity) { + +#ifdef CMSPAR + // Here Installation parity only for GNU/Linux where the macro CMSPAR. + case SerialPort::SpaceParity: + currentTermios.c_cflag &= ~PARODD; + currentTermios.c_cflag |= PARENB | CMSPAR; + break; + case SerialPort::MarkParity: + currentTermios.c_cflag |= PARENB | CMSPAR | PARODD; + break; +#endif + case SerialPort::NoParity: + currentTermios.c_cflag &= ~PARENB; + break; + case SerialPort::EvenParity: + currentTermios.c_cflag &= ~PARODD; + currentTermios.c_cflag |= PARENB; + break; + case SerialPort::OddParity: + currentTermios.c_cflag |= PARENB | PARODD; + break; + default: + currentTermios.c_cflag |= PARENB; + currentTermios.c_iflag |= PARMRK | INPCK; + currentTermios.c_iflag &= ~IGNPAR; + break; + } + + return updateTermios(); +} + +bool SerialPortPrivate::setStopBits(SerialPort::StopBits stopBits) +{ + switch (stopBits) { + case SerialPort::OneStop: + currentTermios.c_cflag &= ~CSTOPB; + break; + case SerialPort::TwoStop: + currentTermios.c_cflag |= CSTOPB; + break; + default: + currentTermios.c_cflag &= ~CSTOPB; + break; + } + return updateTermios(); +} + +bool SerialPortPrivate::setFlowControl(SerialPort::FlowControl flow) +{ + switch (flow) { + case SerialPort::NoFlowControl: + currentTermios.c_cflag &= ~CRTSCTS; + currentTermios.c_iflag &= ~(IXON | IXOFF | IXANY); + break; + case SerialPort::HardwareControl: + currentTermios.c_cflag |= CRTSCTS; + currentTermios.c_iflag &= ~(IXON | IXOFF | IXANY); + break; + case SerialPort::SoftwareControl: + currentTermios.c_cflag &= ~CRTSCTS; + currentTermios.c_iflag |= IXON | IXOFF | IXANY; + break; + default: + currentTermios.c_cflag &= ~CRTSCTS; + currentTermios.c_iflag &= ~(IXON | IXOFF | IXANY); + break; + } + return updateTermios(); +} + +bool SerialPortPrivate::setDataErrorPolicy(SerialPort::DataErrorPolicy policy) +{ + tcflag_t parmrkMask = PARMRK; +#ifndef CMSPAR + // in space/mark parity emulation also used PARMRK flag + if (parity == SerialPort::SpaceParity + || parity == SerialPort::MarkParity) { + parmrkMask = 0; + } +#endif //CMSPAR + switch (policy) { + case SerialPort::SkipPolicy: + currentTermios.c_iflag &= ~parmrkMask; + currentTermios.c_iflag |= IGNPAR | INPCK; + break; + case SerialPort::PassZeroPolicy: + currentTermios.c_iflag &= ~(IGNPAR | parmrkMask); + currentTermios.c_iflag |= INPCK; + break; + case SerialPort::IgnorePolicy: + currentTermios.c_iflag &= ~INPCK; + break; + case SerialPort::StopReceivingPolicy: + currentTermios.c_iflag &= ~IGNPAR; + currentTermios.c_iflag |= parmrkMask | INPCK; + break; + default: + currentTermios.c_iflag &= ~INPCK; + break; + } + return updateTermios(); +} + +bool SerialPortPrivate::readNotification() +{ + // Prevent recursive calls + if (readPortNotifierCalled) { + if (!readPortNotifierStateSet) { + readPortNotifierStateSet = true; + readPortNotifierState = isReadNotificationEnabled(); + setReadNotificationEnabled(false); + } + } + + readPortNotifierCalled = true; + + // Always buffered, read data from the port into the read buffer + qint64 newBytes = readBuffer.size(); + qint64 bytesToRead = policy == SerialPort::IgnorePolicy ? ReadChunkSize : 1; + + if (readBufferMaxSize && bytesToRead > (readBufferMaxSize - readBuffer.size())) { + bytesToRead = readBufferMaxSize - readBuffer.size(); + if (bytesToRead == 0) { + // Buffer is full. User must read data from the buffer + // before we can read more from the port. + return false; + } + } + + char *ptr = readBuffer.reserve(bytesToRead); + const qint64 readBytes = readFromPort(ptr, bytesToRead); + if (readBytes == -2) { + // No bytes currently available for reading. + readBuffer.chop(bytesToRead); + return true; + } + readBuffer.chop(bytesToRead - qMax(readBytes, qint64(0))); + + newBytes = readBuffer.size() - newBytes; + + // If read buffer is full, disable the read port notifier. + if (readBufferMaxSize && readBuffer.size() == readBufferMaxSize) + setReadNotificationEnabled(false); + + // only emit readyRead() when not recursing, and only if there is data available + const bool hasData = newBytes > 0; + + if (!emittedReadyRead && hasData) { + emittedReadyRead = true; + emit q_ptr->readyRead(); + emittedReadyRead = false; + } + + if (!hasData) + setReadNotificationEnabled(true); + + // reset the read port notifier state if we reentered inside the + // readyRead() connected slot. + if (readPortNotifierStateSet + && readPortNotifierState != isReadNotificationEnabled()) { + setReadNotificationEnabled(readPortNotifierState); + readPortNotifierStateSet = false; + } + return true; +} + +bool SerialPortPrivate::writeNotification(int maxSize) +{ + const int tmp = writeBuffer.size(); + + if (writeBuffer.isEmpty()) { + setWriteNotificationEnabled(false); + return false; + } + + int nextSize = qMin(writeBuffer.nextDataBlockSize(), maxSize); + + const char *ptr = writeBuffer.readPointer(); + + // Attempt to write it chunk. + qint64 written = writeToPort(ptr, nextSize); + if (written < 0) { + // TODO: set error? + return false; + } + + // Remove what we wrote so far. + writeBuffer.free(written); + if (written > 0) { + // Don't emit bytesWritten() recursively. + if (!emittedBytesWritten) { + emittedBytesWritten = true; + emit q_ptr->bytesWritten(written); + emittedBytesWritten = false; + } + } + + if (writeBuffer.isEmpty()) + setWriteNotificationEnabled(false); + + return (writeBuffer.size() < tmp); +} + +bool SerialPortPrivate::exceptionNotification() +{ + // FIXME: + return false; +} + +bool SerialPortPrivate::updateTermios() +{ + if (::tcsetattr(descriptor, TCSANOW, ¤tTermios) == -1) { + portError = decodeSystemError(); + return false; + } + return true; +} + +void SerialPortPrivate::detectDefaultSettings() +{ + // Detect rate. + const speed_t inputUnixRate = ::cfgetispeed(¤tTermios); + const speed_t outputUnixRate = ::cfgetospeed(¤tTermios); + bool isCustomRateCurrentSet = false; + +#ifdef Q_OS_LINUX + // try detect the ability to support custom rate + isCustomRateSupported = ::ioctl(descriptor, TIOCGSERIAL, ¤tSerialInfo) != -1 + && ::ioctl(descriptor, TIOCSSERIAL, ¤tSerialInfo) != -1; + + if (isCustomRateSupported) { + restoredSerialInfo = currentSerialInfo; + + // assume that the baud rate is a custom + isCustomRateCurrentSet = inputUnixRate == B38400 && outputUnixRate == B38400; + + if (isCustomRateCurrentSet) { + if ((currentSerialInfo.flags & ASYNC_SPD_CUST) + && currentSerialInfo.custom_divisor > 0) { + + // yes, speed is really custom + inputRate = currentSerialInfo.baud_base / currentSerialInfo.custom_divisor; + outputRate = inputRate; + } else { + // no, we were wrong and the speed is a standard 38400 baud + isCustomRateCurrentSet = false; + } + } + } +#else + // other *nix +#endif + if (!isCustomRateSupported || !isCustomRateCurrentSet) { + inputRate = SerialPortPrivate::rateFromSetting(inputUnixRate); + outputRate = SerialPortPrivate::rateFromSetting(outputUnixRate); + } + + // Detect databits. + switch (currentTermios.c_cflag & CSIZE) { + case CS5: + dataBits = SerialPort::Data5; + break; + case CS6: + dataBits = SerialPort::Data6; + break; + case CS7: + dataBits = SerialPort::Data7; + break; + case CS8: + dataBits = SerialPort::Data8; + break; + default: + dataBits = SerialPort::UnknownDataBits; + break; + } + + // Detect parity. +#ifdef CMSPAR + if (currentTermios.c_cflag & CMSPAR) { + parity = currentTermios.c_cflag & PARODD ? + SerialPort::MarkParity : SerialPort::SpaceParity; + } else { +#endif + if (currentTermios.c_cflag & PARENB) { + parity = currentTermios.c_cflag & PARODD ? + SerialPort::OddParity : SerialPort::EvenParity; + } else { + parity = SerialPort::NoParity; + } +#ifdef CMSPAR + } +#endif + + // Detect stopbits. + stopBits = currentTermios.c_cflag & CSTOPB ? + SerialPort::TwoStop : SerialPort::OneStop; + + // Detect flow control. + if ((!(currentTermios.c_cflag & CRTSCTS)) && (!(currentTermios.c_iflag & (IXON | IXOFF | IXANY)))) + flow = SerialPort::NoFlowControl; + else if ((!(currentTermios.c_cflag & CRTSCTS)) && (currentTermios.c_iflag & (IXON | IXOFF | IXANY))) + flow = SerialPort::SoftwareControl; + else if ((currentTermios.c_cflag & CRTSCTS) && (!(currentTermios.c_iflag & (IXON | IXOFF | IXANY)))) + flow = SerialPort::HardwareControl; + else + flow = SerialPort::UnknownFlowControl; +} + +SerialPort::PortError SerialPortPrivate::decodeSystemError() const +{ + SerialPort::PortError error; + switch (errno) { + case ENODEV: + error = SerialPort::NoSuchDeviceError; + break; + case EACCES: + error = SerialPort::PermissionDeniedError; + break; + case EBUSY: + error = SerialPort::PermissionDeniedError; + break; + case ENOTTY: + error = SerialPort::IoError; + break; + default: + error = SerialPort::UnknownPortError; + break; + } + return error; +} + +bool SerialPortPrivate::isReadNotificationEnabled() const +{ + return readNotifier && readNotifier->isEnabled(); +} + +void SerialPortPrivate::setReadNotificationEnabled(bool enable) +{ + if (readNotifier) { + readNotifier->setEnabled(enable); + } else if (enable) { + readNotifier = new ReadNotifier(this, q_ptr); + readNotifier->setEnabled(true); + } +} + +bool SerialPortPrivate::isWriteNotificationEnabled() const +{ + return writeNotifier && writeNotifier->isEnabled(); +} + +void SerialPortPrivate::setWriteNotificationEnabled(bool enable) +{ + if (writeNotifier) { + writeNotifier->setEnabled(enable); + } else if (enable) { + writeNotifier = new WriteNotifier(this, q_ptr); + writeNotifier->setEnabled(true); + } +} + +bool SerialPortPrivate::isExceptionNotificationEnabled() const +{ + return exceptionNotifier && exceptionNotifier->isEnabled(); +} + +void SerialPortPrivate::setExceptionNotificationEnabled(bool enable) +{ + if (exceptionNotifier) { + exceptionNotifier->setEnabled(enable); + } else if (enable) { + exceptionNotifier = new ExceptionNotifier(this, q_ptr); + exceptionNotifier->setEnabled(true); + } +} + +bool SerialPortPrivate::waitForReadOrWrite(bool *selectForRead, bool *selectForWrite, + bool checkRead, bool checkWrite, + int msecs, bool *timedOut) +{ + Q_ASSERT(selectForRead); + Q_ASSERT(selectForWrite); + Q_ASSERT(timedOut); + + fd_set fdread; + FD_ZERO(&fdread); + if (checkRead) + FD_SET(descriptor, &fdread); + + fd_set fdwrite; + FD_ZERO(&fdwrite); + if (checkWrite) + FD_SET(descriptor, &fdwrite); + + struct timeval tv; + tv.tv_sec = msecs / 1000; + tv.tv_usec = (msecs % 1000) * 1000; + + int ret = ::select(descriptor + 1, &fdread, &fdwrite, 0, msecs < 0 ? 0 : &tv); + if (ret < 0) + return false; + if (ret == 0) { + *timedOut = true; + return false; + } + + *selectForRead = FD_ISSET(descriptor, &fdread); + *selectForWrite = FD_ISSET(descriptor, &fdwrite); + + return ret; +} + +qint64 SerialPortPrivate::readFromPort(char *data, qint64 maxSize) +{ + qint64 bytesRead = 0; +#if defined (CMSPAR) + if (parity == SerialPort::NoParity + || policy != SerialPort::StopReceivingPolicy) { +#else + if (parity != SerialPort::MarkParity + && parity != SerialPort::SpaceParity) { +#endif + bytesRead = ::read(descriptor, data, maxSize); + } else {// Perform parity emulation. + bytesRead = readPerChar(data, maxSize); + } + + // FIXME: Here 'errno' codes for sockets. + // You need to replace the codes for the serial port. + if (bytesRead < 0) { + bytesRead = -1; + switch (errno) { +#if EWOULDBLOCK-0 && EWOULDBLOCK != EAGAIN + case EWOULDBLOCK: +#endif + case EAGAIN: + // No data was available for reading. + bytesRead = -2; + break; + case EBADF: + case EINVAL: + case EIO: + break; + case ECONNRESET: + bytesRead = 0; + break; + default: + break; + } + } + return bytesRead; +} + +qint64 SerialPortPrivate::writeToPort(const char *data, qint64 maxSize) +{ + qint64 bytesWritten = 0; +#if defined (CMSPAR) + bytesWritten = ::write(descriptor, data, maxSize); +#else + if (parity != SerialPort::MarkParity + && parity != SerialPort::SpaceParity) { + bytesWritten = ::write(descriptor, data, maxSize); + } else {// Perform parity emulation. + bytesWritten = writePerChar(data, maxSize); + } +#endif + + // FIXME: Here 'errno' codes for sockets. + // You need to replace the codes for the serial port. + if (bytesWritten < 0) { + switch (errno) { + case EPIPE: + case ECONNRESET: + bytesWritten = -1; + break; + case EAGAIN: + bytesWritten = 0; + break; + case EMSGSIZE: + break; + default: + break; + } + } + return bytesWritten; +} + +static inline bool evenParity(quint8 c) +{ + c ^= c >> 4; //(c7 ^ c3)(c6 ^ c2)(c5 ^ c1)(c4 ^ c0) + c ^= c >> 2; //[(c7 ^ c3)(c5 ^ c1)][(c6 ^ c2)(c4 ^ c0)] + c ^= c >> 1; + return c & 1; //(c7 ^ c3)(c5 ^ c1)(c6 ^ c2)(c4 ^ c0) +} + +#ifndef CMSPAR + +qint64 SerialPortPrivate::writePerChar(const char *data, qint64 maxSize) +{ + qint64 ret = 0; + quint8 const charMask = (0xFF >> (8 - dataBits)); + + while (ret < maxSize) { + + bool par = evenParity(*data & charMask); + // False if need EVEN, true if need ODD. + par ^= parity == SerialPort::MarkParity; + if (par ^ (currentTermios.c_cflag & PARODD)) { // Need switch parity mode? + currentTermios.c_cflag ^= PARODD; + flush(); //force sending already buffered data, because updateTermios() cleares buffers + //todo: add receiving buffered data!!! + if (!updateTermios()) + break; + } + + int r = ::write(descriptor, data, 1); + if (r < 0) + return -1; + if (r > 0) { + data += r; + ret += r; + } + } + return ret; +} + +#endif //CMSPAR + +qint64 SerialPortPrivate::readPerChar(char *data, qint64 maxSize) +{ + qint64 ret = 0; + quint8 const charMask = (0xFF >> (8 - dataBits)); + + // 0 - prefix not started, + // 1 - received 0xFF, + // 2 - received 0xFF and 0x00 + int prefix = 0; + while (ret < maxSize) { + + qint64 r = ::read(descriptor, data, 1); + if (r < 0) { + if (errno == EAGAIN) // It is ok for nonblocking mode. + break; + return -1; + } + if (r == 0) + break; + + bool par = true; + switch (prefix) { + case 2: // Previously received both 0377 and 0. + par = false; + prefix = 0; + break; + case 1: // Previously received 0377. + if (*data == '\0') { + ++prefix; + continue; + } + prefix = 0; + break; + default: + if (*data == '\377') { + prefix = 1; + continue; + } + break; + } + // Now: par contains parity ok or error, *data contains received character + par ^= evenParity(*data & charMask); //par contains parity bit value for EVEN mode + par ^= (currentTermios.c_cflag & PARODD); //par contains parity bit value for current mode + if (par ^ (parity == SerialPort::SpaceParity)) { //if parity error + switch (policy) { + case SerialPort::SkipPolicy: + continue; //ignore received character + case SerialPort::StopReceivingPolicy: + if (parity != SerialPort::NoParity) + portError = SerialPort::ParityError; + else + portError = *data == '\0' ? + SerialPort::BreakConditionError : SerialPort::FramingError; + return ++ret; //abort receiving + break; + case SerialPort::UnknownPolicy: + // Unknown error policy is used! Falling back to PassZeroPolicy + case SerialPort::PassZeroPolicy: + *data = '\0'; //replace received character by zero + break; + case SerialPort::IgnorePolicy: + break; //ignore error and pass received character + } + } + ++data; + ++ret; + } + return ret; +} + +#ifdef Q_OS_MAC +static const QLatin1String defaultPathPrefix("/dev/cu."); +static const QLatin1String notUsedPathPrefix("/dev/tty."); +#else +static const QLatin1String defaultPathPrefix("/dev/"); +#endif + +QString SerialPortPrivate::portNameToSystemLocation(const QString &port) +{ + QString ret = port; + +#ifdef Q_OS_MAC + ret.remove(notUsedPathPrefix); +#endif + + if (!ret.contains(defaultPathPrefix)) + ret.prepend(defaultPathPrefix); + return ret; +} + +QString SerialPortPrivate::portNameFromSystemLocation(const QString &location) +{ + QString ret = location; + +#ifdef Q_OS_MAC + ret.remove(notUsedPathPrefix); +#endif + + ret.remove(defaultPathPrefix); + return ret; +} + +struct RatePair +{ + qint32 rate; // The numerical value of baud rate. + qint32 setting; // The OS-specific code of baud rate. + bool operator<(const RatePair &other) const { return rate < other.rate; } + bool operator==(const RatePair &other) const { return setting == other.setting; } +}; + +// This table contains correspondences standard pairs values of +// baud rates that are defined in file termios.h +static const RatePair standardRatesTable[] = +{ + #ifdef B50 + { 50, B50 }, + #endif + #ifdef B75 + { 75, B75 }, + #endif + #ifdef B110 + { 110, B110 }, + #endif + #ifdef B134 + { 134, B134 }, + #endif + #ifdef B150 + { 150, B150 }, + #endif + #ifdef B200 + { 200, B200 }, + #endif + #ifdef B300 + { 300, B300 }, + #endif + #ifdef B600 + { 600, B600 }, + #endif + #ifdef B1200 + { 1200, B1200 }, + #endif + #ifdef B1800 + { 1800, B1800 }, + #endif + #ifdef B2400 + { 2400, B2400 }, + #endif + #ifdef B4800 + { 4800, B4800 }, + #endif + #ifdef B9600 + { 9600, B9600 }, + #endif + #ifdef B19200 + { 19200, B19200 }, + #endif + #ifdef B38400 + { 38400, B38400 }, + #endif + #ifdef B57600 + { 57600, B57600 }, + #endif + #ifdef B115200 + { 115200, B115200 }, + #endif + #ifdef B230400 + { 230400, B230400 }, + #endif + #ifdef B460800 + { 460800, B460800 }, + #endif + #ifdef B500000 + { 500000, B500000 }, + #endif + #ifdef B576000 + { 576000, B576000 }, + #endif + #ifdef B921600 + { 921600, B921600 }, + #endif + #ifdef B1000000 + { 1000000, B1000000 }, + #endif + #ifdef B1152000 + { 1152000, B1152000 }, + #endif + #ifdef B1500000 + { 1500000, B1500000 }, + #endif + #ifdef B2000000 + { 2000000, B2000000}, + #endif + #ifdef B2500000 + { 2500000, B2500000 }, + #endif + #ifdef B3000000 + { 3000000, B3000000 }, + #endif + #ifdef B3500000 + { 3500000, B3500000 }, + #endif + #ifdef B4000000 + { 4000000, B4000000 } + #endif +}; + +static const RatePair *standardRatesTable_end = + standardRatesTable + sizeof(standardRatesTable)/sizeof(*standardRatesTable); + +qint32 SerialPortPrivate::rateFromSetting(qint32 setting) +{ + const RatePair rp = { 0, setting }; + const RatePair *ret = qFind(standardRatesTable, standardRatesTable_end, rp); + return ret != standardRatesTable_end ? ret->rate : 0; +} + +qint32 SerialPortPrivate::settingFromRate(qint32 rate) +{ + const RatePair rp = { rate, 0 }; + const RatePair *ret = qBinaryFind(standardRatesTable, standardRatesTable_end, rp); + return ret != standardRatesTable_end ? ret->setting : 0; +} + +QList<qint32> SerialPortPrivate::standardRates() +{ + QList<qint32> ret; + for (const RatePair *it = standardRatesTable; it != standardRatesTable_end; ++it) + ret.append(it->rate); + return ret; +} + +QT_END_NAMESPACE_SERIALPORT diff --git a/src/serialport/serialport_unix_p.h b/src/serialport/serialport_unix_p.h new file mode 100644 index 0000000..76a6494 --- /dev/null +++ b/src/serialport/serialport_unix_p.h @@ -0,0 +1,153 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Denis Shienkov <scapig@yandex.ru> +** Copyright (C) 2012 Laszlo Papp <lpapp@kde.org> +** Contact: http://www.qt-project.org/ +** +** This file is part of the QtSerialPort module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this +** file. Please review the following information to ensure the GNU Lesser +** General Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef SERIALPORT_UNIX_P_H +#define SERIALPORT_UNIX_P_H + +#include "serialport_p.h" + +#include <termios.h> +#ifdef Q_OS_LINUX +# include <linux/serial.h> +#endif + +class QSocketNotifier; + +QT_BEGIN_NAMESPACE_SERIALPORT + +class SerialPortPrivate : public SerialPortPrivateData +{ +public: + SerialPortPrivate(SerialPort *q); + + bool open(QIODevice::OpenMode mode); + void close(); + + SerialPort::Lines lines() const; + + bool setDtr(bool set); + bool setRts(bool set); + + bool flush(); + bool clear(SerialPort::Directions dir); + + bool sendBreak(int duration); + bool setBreak(bool set); + + qint64 bytesAvailable() const; + qint64 bytesToWrite() const; + + qint64 readFromBuffer(char *data, qint64 maxSize); + qint64 writeToBuffer(const char *data, qint64 maxSize); + + bool waitForReadyRead(int msecs); + bool waitForBytesWritten(int msecs); + + bool setRate(qint32 rate, SerialPort::Directions dir); + bool setDataBits(SerialPort::DataBits dataBits); + bool setParity(SerialPort::Parity parity); + bool setStopBits(SerialPort::StopBits stopBits); + bool setFlowControl(SerialPort::FlowControl flow); + bool setDataErrorPolicy(SerialPort::DataErrorPolicy policy); + + bool readNotification(); + bool writeNotification(int maxSize = INT_MAX); + bool exceptionNotification(); + + static QString portNameToSystemLocation(const QString &port); + static QString portNameFromSystemLocation(const QString &location); + + static qint32 rateFromSetting(qint32 setting); + static qint32 settingFromRate(qint32 rate); + + static QList<qint32> standardRates(); + + struct termios currentTermios; + struct termios restoredTermios; +#ifdef Q_OS_LINUX + struct serial_struct currentSerialInfo; + struct serial_struct restoredSerialInfo; +#endif + int descriptor; + bool isCustomRateSupported; + + QSocketNotifier *readNotifier; + QSocketNotifier *writeNotifier; + QSocketNotifier *exceptionNotifier; + + bool readPortNotifierCalled; + bool readPortNotifierState; + bool readPortNotifierStateSet; + + bool emittedReadyRead; + bool emittedBytesWritten; + +private: + bool updateTermios(); + + void detectDefaultSettings(); + SerialPort::PortError decodeSystemError() const; + + bool isReadNotificationEnabled() const; + void setReadNotificationEnabled(bool enable); + bool isWriteNotificationEnabled() const; + void setWriteNotificationEnabled(bool enable); + bool isExceptionNotificationEnabled() const; + void setExceptionNotificationEnabled(bool enable); + + bool waitForReadOrWrite(bool *selectForRead, bool *selectForWrite, + bool checkRead, bool checkWrite, + int msecs, bool *timedOut); + + qint64 readFromPort(char *data, qint64 maxSize); + qint64 writeToPort(const char *data, qint64 maxSize); + +#ifndef CMSPAR + qint64 writePerChar(const char *data, qint64 maxSize); +#endif + qint64 readPerChar(char *data, qint64 maxSize); + +}; + +QT_END_NAMESPACE_SERIALPORT + +#endif // SERIALPORT_UNIX_P_H diff --git a/src/serialport/serialport_win.cpp b/src/serialport/serialport_win.cpp new file mode 100644 index 0000000..af43f79 --- /dev/null +++ b/src/serialport/serialport_win.cpp @@ -0,0 +1,1091 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Denis Shienkov <scapig@yandex.ru> +** Copyright (C) 2012 Laszlo Papp <lpapp@kde.org> +** Copyright (C) 2012 Andre Hartmann <aha_1980@gmx.de> +** Contact: http://www.qt-project.org/ +** +** This file is part of the QtSerialPort module 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 "serialport_win_p.h" + +#include <QtCore/qelapsedtimer.h> + +#if (QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)) +#include <QtCore/qwineventnotifier.h> +#else +#include "qt4support/qwineventnotifier_p.h" +#endif + +#include <QDebug> + +#ifndef CTL_CODE +# define CTL_CODE(DeviceType, Function, Method, Access) ( \ + ((DeviceType) << 16) | ((Access) << 14) | ((Function) << 2) | (Method) \ + ) +#endif + +#ifndef FILE_DEVICE_SERIAL_PORT +# define FILE_DEVICE_SERIAL_PORT 27 +#endif + +#ifndef METHOD_BUFFERED +# define METHOD_BUFFERED 0 +#endif + +#ifndef FILE_ANY_ACCESS +# define FILE_ANY_ACCESS 0x00000000 +#endif + +#ifndef IOCTL_SERIAL_GET_DTRRTS +# define IOCTL_SERIAL_GET_DTRRTS \ + CTL_CODE(FILE_DEVICE_SERIAL_PORT, 30, METHOD_BUFFERED, FILE_ANY_ACCESS) +#endif + +#ifndef SERIAL_DTR_STATE +# define SERIAL_DTR_STATE 0x00000001 +#endif + +#ifndef SERIAL_RTS_STATE +# define SERIAL_RTS_STATE 0x00000002 +#endif + +QT_BEGIN_NAMESPACE_SERIALPORT + +#ifndef Q_OS_WINCE + +class CommEventNotifier : public QWinEventNotifier +{ +public: + CommEventNotifier(SerialPortPrivate *d, QObject *parent) + : QWinEventNotifier(d->eventOverlapped.hEvent, parent) + , dptr(d) + { + } + +protected: + virtual bool event(QEvent *e) { + const bool ret = QWinEventNotifier::event(e); + if (ret) { + if (EV_ERR & dptr->eventMask) + dptr->processIoErrors(); + if (EV_RXCHAR & dptr->eventMask) { + if (!dptr->readSequenceStarted) + dptr->startAsyncRead(); + } + ::WaitCommEvent(dptr->descriptor, &dptr->eventMask, &dptr->eventOverlapped); + } + return ret; + } + +private: + SerialPortPrivate *dptr; +}; + +class ReadCompletionNotifier : public QWinEventNotifier +{ +public: + ReadCompletionNotifier(SerialPortPrivate *d, QObject *parent) + : QWinEventNotifier(d->readOverlapped.hEvent, parent) + , dptr(d) + {} + +protected: + virtual bool event(QEvent *e) { + bool ret = QWinEventNotifier::event(e); + if (ret) { + DWORD numberOfBytesTransferred = 0; + BOOL success = ::GetOverlappedResult(dptr->descriptor, + &dptr->readOverlapped, + &numberOfBytesTransferred, + FALSE); + if (success) + dptr->completeAsyncRead(numberOfBytesTransferred); + } + return ret; + } + +private: + SerialPortPrivate *dptr; +}; + +class WriteCompletionNotifier : public QWinEventNotifier +{ +public: + WriteCompletionNotifier(SerialPortPrivate *d, QObject *parent) + : QWinEventNotifier(d->writeOverlapped.hEvent, parent) + , dptr(d) + {} + +protected: + virtual bool event(QEvent *e) { + bool ret = QWinEventNotifier::event(e); + if (ret) { + DWORD numberOfBytesTransferred = 0; + BOOL success = ::GetOverlappedResult(dptr->descriptor, + &dptr->writeOverlapped, + &numberOfBytesTransferred, + FALSE); + if (success) + dptr->completeAsyncWrite(numberOfBytesTransferred); + } + return ret; + } + +private: + SerialPortPrivate *dptr; +}; + +SerialPortPrivate::SerialPortPrivate(SerialPort *q) + : SerialPortPrivateData(q) + , descriptor(INVALID_HANDLE_VALUE) + , flagErrorFromCommEvent(false) + , eventMask(EV_ERR) + , eventNotifier(0) + , readCompletionNotifier(0) + , writeCompletionNotifier(0) + , actualReadBufferSize(0) + , actualWriteBufferSize(0) + , readyReadEmitted(0) + , readSequenceStarted(false) + , writeSequenceStarted(false) +{ +} + +bool SerialPortPrivate::open(QIODevice::OpenMode mode) +{ + DWORD desiredAccess = 0; + eventMask = EV_ERR; + + if (mode & QIODevice::ReadOnly) { + desiredAccess |= GENERIC_READ; + eventMask |= EV_RXCHAR; + } + if (mode & QIODevice::WriteOnly) { + desiredAccess |= GENERIC_WRITE; + eventMask |= EV_TXEMPTY; + } + + descriptor = ::CreateFile(reinterpret_cast<const wchar_t*>(systemLocation.utf16()), + desiredAccess, 0, NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL); + + if (descriptor == INVALID_HANDLE_VALUE) { + portError = decodeSystemError(); + return false; + } + + if (!::GetCommState(descriptor, &restoredDcb)) { + portError = decodeSystemError(); + return false; + } + + currentDcb = restoredDcb; + currentDcb.fBinary = TRUE; + currentDcb.fInX = FALSE; + currentDcb.fOutX = FALSE; + currentDcb.fAbortOnError = FALSE; + currentDcb.fNull = FALSE; + currentDcb.fErrorChar = FALSE; + + if (!updateDcb()) + return false; + + if (!::GetCommTimeouts(descriptor, &restoredCommTimeouts)) { + portError = decodeSystemError(); + return false; + } + + ::memset(¤tCommTimeouts, 0, sizeof(currentCommTimeouts)); + currentCommTimeouts.ReadIntervalTimeout = MAXDWORD; + + if (!updateCommTimeouts()) + return false; + + ::memset(&selectOverlapped, 0, sizeof(selectOverlapped)); + selectOverlapped.hEvent = ::CreateEvent(NULL, TRUE, FALSE, NULL); + + if (eventMask & EV_RXCHAR) { + ::memset(&readOverlapped, 0, sizeof(readOverlapped)); + readOverlapped.hEvent = ::CreateEvent(NULL, FALSE, FALSE, NULL); + readCompletionNotifier = new ReadCompletionNotifier(this, q_ptr); + readCompletionNotifier->setEnabled(true); + } + + if (eventMask & EV_TXEMPTY) { + // Disable EV_TXEMPTY for CommEventNotifier because not all serial ports + // (such as from Bluetooth stack of Microsoft) supported this feature. + // I.e. now do not use this event to write to the port. + eventMask &= ~EV_TXEMPTY; + ::memset(&writeOverlapped, 0, sizeof(writeOverlapped)); + writeOverlapped.hEvent = ::CreateEvent(NULL, FALSE, FALSE, NULL); + writeCompletionNotifier = new WriteCompletionNotifier(this, q_ptr); + writeCompletionNotifier->setEnabled(true); + } + + ::SetCommMask(descriptor, eventMask); + ::memset(&eventOverlapped, 0, sizeof(eventOverlapped)); + eventOverlapped.hEvent = ::CreateEvent(NULL, TRUE, FALSE, NULL); + eventNotifier = new CommEventNotifier(this, q_ptr); + eventNotifier->setEnabled(true); + ::WaitCommEvent(descriptor, &eventMask, &eventOverlapped); + + detectDefaultSettings(); + return true; +} + +void SerialPortPrivate::close() +{ + ::CancelIo(descriptor); + + if (eventNotifier) { + eventNotifier->setEnabled(false); + ::CancelIo(eventOverlapped.hEvent); + ::CloseHandle(eventOverlapped.hEvent); + eventNotifier->deleteLater(); + eventNotifier = 0; + } + + if (readCompletionNotifier) { + readCompletionNotifier->setEnabled(false); + ::CancelIo(readOverlapped.hEvent); + ::CloseHandle(readOverlapped.hEvent); + readCompletionNotifier->deleteLater(); + readCompletionNotifier = 0; + } + + if (readSequenceStarted) + readSequenceStarted = false; + + readBuffer.clear(); + actualReadBufferSize = 0; + + if (writeCompletionNotifier) { + writeCompletionNotifier->setEnabled(false); + ::CancelIo(writeOverlapped.hEvent); + ::CloseHandle(writeOverlapped.hEvent); + writeCompletionNotifier->deleteLater(); + writeCompletionNotifier = 0; + } + + if (writeSequenceStarted) + writeSequenceStarted = false; + + writeBuffer.clear(); + actualWriteBufferSize = 0; + + readyReadEmitted = false; + flagErrorFromCommEvent = false; + + if (restoreSettingsOnClose) { + ::SetCommState(descriptor, &restoredDcb); + ::SetCommTimeouts(descriptor, &restoredCommTimeouts); + } + + ::CloseHandle(descriptor); + descriptor = INVALID_HANDLE_VALUE; +} + +#endif // #ifndef Q_OS_WINCE + +SerialPort::Lines SerialPortPrivate::lines() const +{ + DWORD modemStat = 0; + SerialPort::Lines ret = 0; + + if (!::GetCommModemStatus(descriptor, &modemStat)) + return ret; + + if (modemStat & MS_CTS_ON) + ret |= SerialPort::Cts; + if (modemStat & MS_DSR_ON) + ret |= SerialPort::Dsr; + if (modemStat & MS_RING_ON) + ret |= SerialPort::Ri; + if (modemStat & MS_RLSD_ON) + ret |= SerialPort::Dcd; + + DWORD bytesReturned = 0; + if (::DeviceIoControl(descriptor, IOCTL_SERIAL_GET_DTRRTS, NULL, 0, + &modemStat, sizeof(modemStat), + &bytesReturned, NULL)) { + + if (modemStat & SERIAL_DTR_STATE) + ret |= SerialPort::Dtr; + if (modemStat & SERIAL_RTS_STATE) + ret |= SerialPort::Rts; + } + + return ret; +} + +bool SerialPortPrivate::setDtr(bool set) +{ + return ::EscapeCommFunction(descriptor, set ? SETDTR : CLRDTR); +} + +bool SerialPortPrivate::setRts(bool set) +{ + return ::EscapeCommFunction(descriptor, set ? SETRTS : CLRRTS); +} + +#ifndef Q_OS_WINCE + +bool SerialPortPrivate::flush() +{ + return startAsyncWrite() && ::FlushFileBuffers(descriptor); +} + +#endif + +bool SerialPortPrivate::clear(SerialPort::Directions dir) +{ + DWORD flags = 0; + if (dir & SerialPort::Input) + flags |= PURGE_RXABORT | PURGE_RXCLEAR; + if (dir & SerialPort::Output) + flags |= PURGE_TXABORT | PURGE_TXCLEAR; + return ::PurgeComm(descriptor, flags); +} + +bool SerialPortPrivate::sendBreak(int duration) +{ + // FIXME: + if (setBreak(true)) { + ::Sleep(duration); + if (setBreak(false)) + return true; + } + return false; +} + +bool SerialPortPrivate::setBreak(bool set) +{ + if (set) + return ::SetCommBreak(descriptor); + return ::ClearCommBreak(descriptor); +} + +qint64 SerialPortPrivate::bytesAvailable() const +{ + COMSTAT cs; + ::memset(&cs, 0, sizeof(cs)); + if (!::ClearCommError(descriptor, NULL, &cs)) + return -1; + return cs.cbInQue; +} + +qint64 SerialPortPrivate::bytesToWrite() const +{ + COMSTAT cs; + ::memset(&cs, 0, sizeof(cs)); + if (!::ClearCommError(descriptor, NULL, &cs)) + return -1; + return cs.cbOutQue; +} + +#ifndef Q_OS_WINCE + +qint64 SerialPortPrivate::readFromBuffer(char *data, qint64 maxSize) +{ + if (actualReadBufferSize == 0) + return 0; + + qint64 readSoFar = -1; + if (maxSize == 1 && actualReadBufferSize > 0) { + *data = readBuffer.getChar(); + actualReadBufferSize--; + readSoFar = 1; + } else { + const qint64 bytesToRead = qMin(qint64(actualReadBufferSize), maxSize); + readSoFar = 0; + while (readSoFar < bytesToRead) { + const char *ptr = readBuffer.readPointer(); + const int bytesToReadFromThisBlock = qMin(bytesToRead - readSoFar, + qint64(readBuffer.nextDataBlockSize())); + ::memcpy(data + readSoFar, ptr, bytesToReadFromThisBlock); + readSoFar += bytesToReadFromThisBlock; + readBuffer.free(bytesToReadFromThisBlock); + actualReadBufferSize -= bytesToReadFromThisBlock; + } + } + + if (!readSequenceStarted) + startAsyncRead(); + + return readSoFar; +} + +qint64 SerialPortPrivate::writeToBuffer(const char *data, qint64 maxSize) +{ + char *ptr = writeBuffer.reserve(maxSize); + if (maxSize == 1) { + *ptr = *data; + actualWriteBufferSize++; + } else { + ::memcpy(ptr, data, maxSize); + actualWriteBufferSize += maxSize; + } + + if (!writeSequenceStarted) + startAsyncWrite(WriteChunkSize); + + return maxSize; +} + +bool SerialPortPrivate::waitForReadyRead(int msecs) +{ + QElapsedTimer stopWatch; + + stopWatch.start(); + + do { + bool readyToStartRead = false; + bool readyToStartWrite = false; + bool readyToCompleteRead = false; + bool readyToCompleteWrite = false; + bool timedOut = false; + if (!waitForReadOrWrite(&readyToStartRead, &readyToStartWrite, + &readyToCompleteRead, &readyToCompleteWrite, + true, !writeBuffer.isEmpty(), + timeoutValue(msecs, stopWatch.elapsed()), &timedOut)) { + // This is occur timeout or another error + // TODO: set error ? + return false; + } + + if (readyToStartRead) { + if (!startAsyncRead()) + return false; + } + + if (readyToStartWrite) { + if (!startAsyncWrite(WriteChunkSize)) + return false; + } + + DWORD bytesTransferred = 0; + + if (readyToCompleteRead) { + if (!::GetOverlappedResult(descriptor, &readOverlapped, + &bytesTransferred, FALSE)) { + return false; + } + return completeAsyncRead(bytesTransferred); + } + + if (readyToCompleteWrite) { + if (::GetOverlappedResult(descriptor, &readOverlapped, + &bytesTransferred, FALSE)) { + completeAsyncWrite(bytesTransferred); + } + } + + } while (msecs == -1 || timeoutValue(msecs, stopWatch.elapsed()) > 0); + return false; +} + +bool SerialPortPrivate::waitForBytesWritten(int msecs) +{ + if (writeBuffer.isEmpty()) + return false; + + QElapsedTimer stopWatch; + + stopWatch.start(); + + forever { + bool readyToStartRead = false; + bool readyToStartWrite = false; + bool readyToCompleteRead = false; + bool readyToCompleteWrite = false; + bool timedOut = false; + if (!waitForReadOrWrite(&readyToStartRead, &readyToStartWrite, + &readyToCompleteRead, &readyToCompleteWrite, + true, !writeBuffer.isEmpty(), + timeoutValue(msecs, stopWatch.elapsed()), &timedOut)) { + // This is occur timeout or another error + // TODO: set error ? + return false; + } + + if (readyToStartRead) { + if (!startAsyncRead()) + return false; + } + + if (readyToStartWrite) { + startAsyncWrite(WriteChunkSize); + } + + DWORD bytesTransferred = 0; + + if (readyToCompleteRead) { + if (!::GetOverlappedResult(descriptor, &readOverlapped, + &bytesTransferred, FALSE)) { + return false; + } + if (!completeAsyncRead(bytesTransferred)) + return false; + } + + if (readyToCompleteWrite) { + if (::GetOverlappedResult(descriptor, &readOverlapped, + &bytesTransferred, FALSE)) { + if (completeAsyncWrite(bytesTransferred)) + return true; + } + } + + } + return false; +} + +#endif // #ifndef Q_OS_WINCE + +bool SerialPortPrivate::setRate(qint32 rate, SerialPort::Directions dir) +{ + if (dir != SerialPort::AllDirections) { + portError = SerialPort::UnsupportedPortOperationError; + return false; + } + currentDcb.BaudRate = rate; + return updateDcb(); +} + +bool SerialPortPrivate::setDataBits(SerialPort::DataBits dataBits) +{ + currentDcb.ByteSize = dataBits; + return updateDcb(); +} + +bool SerialPortPrivate::setParity(SerialPort::Parity parity) +{ + currentDcb.fParity = TRUE; + switch (parity) { + case SerialPort::NoParity: + currentDcb.Parity = NOPARITY; + currentDcb.fParity = FALSE; + break; + case SerialPort::OddParity: + currentDcb.Parity = ODDPARITY; + break; + case SerialPort::EvenParity: + currentDcb.Parity = EVENPARITY; + break; + case SerialPort::MarkParity: + currentDcb.Parity = MARKPARITY; + break; + case SerialPort::SpaceParity: + currentDcb.Parity = SPACEPARITY; + break; + default: + currentDcb.Parity = NOPARITY; + currentDcb.fParity = FALSE; + break; + } + return updateDcb(); +} + +bool SerialPortPrivate::setStopBits(SerialPort::StopBits stopBits) +{ + switch (stopBits) { + case SerialPort::OneStop: + currentDcb.StopBits = ONESTOPBIT; + break; + case SerialPort::OneAndHalfStop: + currentDcb.StopBits = ONE5STOPBITS; + break; + case SerialPort::TwoStop: + currentDcb.StopBits = TWOSTOPBITS; + break; + default: + currentDcb.StopBits = ONESTOPBIT; + break; + } + return updateDcb(); +} + +bool SerialPortPrivate::setFlowControl(SerialPort::FlowControl flow) +{ + currentDcb.fInX = FALSE; + currentDcb.fOutX = FALSE; + currentDcb.fOutxCtsFlow = FALSE; + currentDcb.fRtsControl = RTS_CONTROL_DISABLE; + switch (flow) { + case SerialPort::NoFlowControl: + break; + case SerialPort::SoftwareControl: + currentDcb.fInX = TRUE; + currentDcb.fOutX = TRUE; + break; + case SerialPort::HardwareControl: + currentDcb.fOutxCtsFlow = TRUE; + currentDcb.fRtsControl = RTS_CONTROL_HANDSHAKE; + break; + default: + break; + } + return updateDcb(); +} + +bool SerialPortPrivate::setDataErrorPolicy(SerialPort::DataErrorPolicy policy) +{ + policy = policy; + return true; +} + +#ifndef Q_OS_WINCE + +bool SerialPortPrivate::startAsyncRead() +{ + DWORD bytesToRead = policy == SerialPort::IgnorePolicy ? ReadChunkSize : 1; + + if (readBufferMaxSize && bytesToRead > (readBufferMaxSize - readBuffer.size())) { + bytesToRead = readBufferMaxSize - readBuffer.size(); + if (bytesToRead == 0) { + // Buffer is full. User must read data from the buffer + // before we can read more from the port. + return false; + } + } + + char *ptr = readBuffer.reserve(bytesToRead); + + readSequenceStarted = true; + if (::ReadFile(descriptor, ptr, bytesToRead, NULL, &readOverlapped)) + return true; + + switch (::GetLastError()) { + case ERROR_IO_PENDING: + // This is not an error. We're getting notified, when data arrives. + return true; + case ERROR_MORE_DATA: + // This is not an error. The synchronous read succeeded. + break; + default: + // error + readSequenceStarted = false; + return false; + } + + return true; +} + +bool SerialPortPrivate::startAsyncWrite(int maxSize) +{ + writeSequenceStarted = true; + + int nextSize = qMin(writeBuffer.nextDataBlockSize(), maxSize); + + const char *ptr = writeBuffer.readPointer(); + + if (::WriteFile(descriptor, ptr, nextSize, NULL, &writeOverlapped)) + return true; + + switch (::GetLastError()) { + case ERROR_IO_PENDING: + // This is not an error. We're getting notified, when data arrives. + return true; + case ERROR_MORE_DATA: + // This is not an error. The synchronous read succeeded. + break; + default: + // error + writeSequenceStarted = false; + return false; + } + return true; +} + +#endif // #ifndef Q_OS_WINCE + +bool SerialPortPrivate::processIoErrors() +{ + DWORD error = 0; + const bool ret = ::ClearCommError(descriptor, &error, FALSE); + if (ret && error) { + if (error & CE_FRAME) + portError = SerialPort::FramingError; + else if (error & CE_RXPARITY) + portError = SerialPort::ParityError; + else if (error & CE_BREAK) + portError = SerialPort::BreakConditionError; + else + portError = SerialPort::UnknownPortError; + + flagErrorFromCommEvent = true; + } + return ret; +} + +#ifndef Q_OS_WINCE + +bool SerialPortPrivate::completeAsyncRead(DWORD numberOfBytes) +{ + actualReadBufferSize += qint64(numberOfBytes); + readBuffer.truncate(actualReadBufferSize); + + if (numberOfBytes > 0) { + + // Process emulate policy. + if (flagErrorFromCommEvent) { + + flagErrorFromCommEvent = false; + + // Ignore received character, remove it from buffer + if (policy == SerialPort::SkipPolicy) { + readSequenceStarted = false; + readBuffer.getChar(); + startAsyncRead(); + return true; + } + + // Abort receiving + if (policy == SerialPort::StopReceivingPolicy) { + readSequenceStarted = false; + readyReadEmitted = true; + emit q_ptr->readyRead(); + return true; + } + + // Replace received character by zero + if (policy == SerialPort::PassZeroPolicy) { + readBuffer.getChar(); + readBuffer.putChar('\0'); + } + + } + + readyReadEmitted = true; + emit q_ptr->readyRead(); + startAsyncRead(); + } else { + readSequenceStarted = false; + } + return true; +} + +bool SerialPortPrivate::completeAsyncWrite(DWORD numberOfBytes) +{ + writeBuffer.free(numberOfBytes); + actualWriteBufferSize -= qint64(numberOfBytes); + + if (numberOfBytes > 0) + emit q_ptr->bytesWritten(numberOfBytes); + + if (writeBuffer.isEmpty()) + writeSequenceStarted = false; + else + startAsyncWrite(WriteChunkSize); + + return true; +} + +bool SerialPortPrivate::updateDcb() +{ + if (!::SetCommState(descriptor, ¤tDcb)) { + portError = decodeSystemError(); + return false; + } + return true; +} + +bool SerialPortPrivate::updateCommTimeouts() +{ + if (!::SetCommTimeouts(descriptor, ¤tCommTimeouts)) { + portError = decodeSystemError(); + return false; + } + return true; +} + +#endif // #ifndef Q_OS_WINCE + +void SerialPortPrivate::detectDefaultSettings() +{ + // Detect rate. + inputRate = quint32(currentDcb.BaudRate); + outputRate = inputRate; + + // Detect databits. + switch (currentDcb.ByteSize) { + case 5: + dataBits = SerialPort::Data5; + break; + case 6: + dataBits = SerialPort::Data6; + break; + case 7: + dataBits = SerialPort::Data7; + break; + case 8: + dataBits = SerialPort::Data8; + break; + default: + dataBits = SerialPort::UnknownDataBits; + break; + } + + // Detect parity. + if ((currentDcb.Parity == NOPARITY) && !currentDcb.fParity) + parity = SerialPort::NoParity; + else if ((currentDcb.Parity == SPACEPARITY) && currentDcb.fParity) + parity = SerialPort::SpaceParity; + else if ((currentDcb.Parity == MARKPARITY) && currentDcb.fParity) + parity = SerialPort::MarkParity; + else if ((currentDcb.Parity == EVENPARITY) && currentDcb.fParity) + parity = SerialPort::EvenParity; + else if ((currentDcb.Parity == ODDPARITY) && currentDcb.fParity) + parity = SerialPort::OddParity; + else + parity = SerialPort::UnknownParity; + + // Detect stopbits. + switch (currentDcb.StopBits) { + case ONESTOPBIT: + stopBits = SerialPort::OneStop; + break; + case ONE5STOPBITS: + stopBits = SerialPort::OneAndHalfStop; + break; + case TWOSTOPBITS: + stopBits = SerialPort::TwoStop; + break; + default: + stopBits = SerialPort::UnknownStopBits; + break; + } + + // Detect flow control. + if (!currentDcb.fOutxCtsFlow && (currentDcb.fRtsControl == RTS_CONTROL_DISABLE) + && !currentDcb.fInX && !currentDcb.fOutX) { + flow = SerialPort::NoFlowControl; + } else if (!currentDcb.fOutxCtsFlow && (currentDcb.fRtsControl == RTS_CONTROL_DISABLE) + && currentDcb.fInX && currentDcb.fOutX) { + flow = SerialPort::SoftwareControl; + } else if (currentDcb.fOutxCtsFlow && (currentDcb.fRtsControl == RTS_CONTROL_HANDSHAKE) + && !currentDcb.fInX && !currentDcb.fOutX) { + flow = SerialPort::HardwareControl; + } else + flow = SerialPort::UnknownFlowControl; +} + +SerialPort::PortError SerialPortPrivate::decodeSystemError() const +{ + SerialPort::PortError error; + switch (::GetLastError()) { + case ERROR_FILE_NOT_FOUND: + error = SerialPort::NoSuchDeviceError; + break; + case ERROR_ACCESS_DENIED: + error = SerialPort::PermissionDeniedError; + break; + case ERROR_INVALID_HANDLE: + error = SerialPort::DeviceIsNotOpenedError; + break; + case ERROR_INVALID_PARAMETER: + error = SerialPort::UnsupportedPortOperationError; + break; + default: + error = SerialPort::UnknownPortError; + break; + } + return error; +} + +#ifndef Q_OS_WINCE + +bool SerialPortPrivate::waitForReadOrWrite(bool *selectForStartRead, bool *selectForStartWrite, + bool *selectForCompleteRead, bool *selectForCompleteWrite, + bool checkRead, bool checkWrite, + int msecs, bool *timedOut) +{ + Q_ASSERT(selectForStartRead); + Q_ASSERT(selectForStartWrite); + Q_ASSERT(selectForCompleteRead); + Q_ASSERT(selectForCompleteWrite); + + DWORD eventMask = 0; + + if (!::WaitCommEvent(descriptor, &eventMask, &selectOverlapped) + && ::GetLastError() != ERROR_IO_PENDING) { + return false; + } + + enum { + SelectEventIndex = 0, + ReadEventIndex = 1, + WriteEventIndex = 2, + NumberOfEvents = 3 + }; + + HANDLE events[NumberOfEvents] = { + selectOverlapped.hEvent, + readOverlapped.hEvent, + writeOverlapped.hEvent + }; + + DWORD waitResult = ::WaitForMultipleObjects(NumberOfEvents, + events, + FALSE, // wait any event + qMax(msecs, 0)); + + switch (waitResult) { + case WAIT_OBJECT_0 + SelectEventIndex: { + if (checkRead && (eventMask == EV_RXCHAR)) + *selectForStartRead = true; + if (checkWrite && (eventMask == EV_TXEMPTY)) + *selectForStartWrite = true; + } + break; + case WAIT_OBJECT_0 + ReadEventIndex: { + if (checkRead) + *selectForCompleteRead = true; + } + break; + case WAIT_OBJECT_0 + WriteEventIndex: { + if (checkWrite) + *selectForCompleteWrite = true; + } + break; + case WAIT_OBJECT_0 + WAIT_TIMEOUT: { + *timedOut = true; + return false; + } + break; + default: + return false; + } + + return true; +} + +static const QLatin1String defaultPathPrefix("\\\\.\\"); + +QString SerialPortPrivate::portNameToSystemLocation(const QString &port) +{ + QString ret = port; + if (!ret.contains(defaultPathPrefix)) + ret.prepend(defaultPathPrefix); + return ret; +} + +QString SerialPortPrivate::portNameFromSystemLocation(const QString &location) +{ + QString ret = location; + if (ret.contains(defaultPathPrefix)) + ret.remove(defaultPathPrefix); + return ret; +} + +#endif // #ifndef Q_OS_WINCE + +// This table contains standard values of baud rates that +// are defined in MSDN and/or in Win SDK file winbase.h +static const qint32 standardRatesTable[] = +{ + #ifdef CBR_110 + CBR_110, + #endif + #ifdef CBR_300 + CBR_300, + #endif + #ifdef CBR_600 + CBR_600, + #endif + #ifdef CBR_1200 + CBR_1200, + #endif + #ifdef CBR_2400 + CBR_2400, + #endif + #ifdef CBR_4800 + CBR_4800, + #endif + #ifdef CBR_9600 + CBR_9600, + #endif + #ifdef CBR_14400 + CBR_14400, + #endif + #ifdef CBR_19200 + CBR_19200, + #endif + #ifdef CBR_38400 + CBR_38400, + #endif + #ifdef CBR_56000 + CBR_56000, + #endif + #ifdef CBR_57600 + CBR_57600, + #endif + #ifdef CBR_115200 + CBR_115200, + #endif + #ifdef CBR_128000 + CBR_128000, + #endif + #ifdef CBR_256000 + CBR_256000 + #endif +}; + +static const qint32 *standardRatesTable_end = + standardRatesTable + sizeof(standardRatesTable)/sizeof(*standardRatesTable); + +qint32 SerialPortPrivate::rateFromSetting(qint32 setting) +{ + const qint32 *ret = qFind(standardRatesTable, standardRatesTable_end, setting); + return ret != standardRatesTable_end ? *ret : 0; +} + +qint32 SerialPortPrivate::settingFromRate(qint32 rate) +{ + const qint32 *ret = qBinaryFind(standardRatesTable, standardRatesTable_end, rate); + return ret != standardRatesTable_end ? *ret : 0; +} + +QList<qint32> SerialPortPrivate::standardRates() +{ + QList<qint32> l; + for (const qint32 *it = standardRatesTable; it != standardRatesTable_end; ++it) + l.append(*it); + return l; +} + +QT_END_NAMESPACE_SERIALPORT diff --git a/src/serialport/serialport_win_p.h b/src/serialport/serialport_win_p.h new file mode 100644 index 0000000..171f2cb --- /dev/null +++ b/src/serialport/serialport_win_p.h @@ -0,0 +1,163 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Denis Shienkov <scapig@yandex.ru> +** Copyright (C) 2012 Laszlo Papp <lpapp@kde.org> +** Contact: http://www.qt-project.org/ +** +** This file is part of the QtSerialPort module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this +** file. Please review the following information to ensure the GNU Lesser +** General Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef SERIALPORT_WIN_P_H +#define SERIALPORT_WIN_P_H + +#include "serialport_p.h" + +#include <qt_windows.h> + +#ifndef Q_OS_WINCE +class QWinEventNotifier; +#else +class QThread; +#include <QtCore/qmutex.h> +#endif + +QT_BEGIN_NAMESPACE_SERIALPORT + +class SerialPortPrivate : public SerialPortPrivateData +{ +public: + SerialPortPrivate(SerialPort *q); + + bool open(QIODevice::OpenMode mode); + void close(); + + SerialPort::Lines lines() const; + + bool setDtr(bool set); + bool setRts(bool set); + + bool flush(); + bool clear(SerialPort::Directions dir); + + bool sendBreak(int duration); + bool setBreak(bool set); + + qint64 bytesAvailable() const; + qint64 bytesToWrite() const; + + qint64 readFromBuffer(char *data, qint64 maxSize); + qint64 writeToBuffer(const char *data, qint64 maxSize); + + bool waitForReadyRead(int msec); + bool waitForBytesWritten(int msec); + + bool setRate(qint32 rate, SerialPort::Directions dir); + bool setDataBits(SerialPort::DataBits dataBits); + bool setParity(SerialPort::Parity parity); + bool setStopBits(SerialPort::StopBits stopBits); + bool setFlowControl(SerialPort::FlowControl flowControl); + bool setDataErrorPolicy(SerialPort::DataErrorPolicy policy); + + bool processIoErrors(); +#ifndef Q_OS_WINCE + bool startAsyncRead(); + bool startAsyncWrite(int maxSize = INT_MAX); + bool completeAsyncRead(DWORD numberOfBytes); + bool completeAsyncWrite(DWORD numberOfBytes); +#else + bool notifyRead(); + bool notifyWrite(int maxSize = INT_MAX); +#endif + + static QString portNameToSystemLocation(const QString &port); + static QString portNameFromSystemLocation(const QString &location); + + static qint32 rateFromSetting(qint32 setting); + static qint32 settingFromRate(qint32 rate); + + static QList<qint32> standardRates(); + + DCB currentDcb; + DCB restoredDcb; + COMMTIMEOUTS currentCommTimeouts; + COMMTIMEOUTS restoredCommTimeouts; + HANDLE descriptor; + bool flagErrorFromCommEvent; + DWORD eventMask; + +#ifndef Q_OS_WINCE + OVERLAPPED eventOverlapped; + OVERLAPPED readOverlapped; + OVERLAPPED writeOverlapped; + OVERLAPPED selectOverlapped; + + QWinEventNotifier *eventNotifier; + QWinEventNotifier *readCompletionNotifier; + QWinEventNotifier *writeCompletionNotifier; + + qint64 actualReadBufferSize; + qint64 actualWriteBufferSize; + bool readyReadEmitted; + bool readSequenceStarted; + bool writeSequenceStarted; +#else + QThread *eventNotifier; + QMutex settingsChangeMutex; +#endif + +private: + bool updateDcb(); + bool updateCommTimeouts(); + + void detectDefaultSettings(); + SerialPort::PortError decodeSystemError() const; + +#ifndef Q_OS_WINCE + bool waitForReadOrWrite(bool *selectForStartRead, bool *selectForStartWrite, + bool *selectForCompleteRead, bool *selectForCompleteWrite, + bool checkRead, bool checkWrite, + int msecs, bool *timedOut); +#else + bool waitForReadOrWrite(bool *selectForRead, bool *selectForWrite, + bool checkRead, bool checkWrite, + int msecs, bool *timedOut); +#endif + +}; + +QT_END_NAMESPACE_SERIALPORT + +#endif // SERIALPORT_WIN_P_H diff --git a/src/serialport/serialport_wince.cpp b/src/serialport/serialport_wince.cpp new file mode 100644 index 0000000..7489747 --- /dev/null +++ b/src/serialport/serialport_wince.cpp @@ -0,0 +1,475 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Denis Shienkov <scapig@yandex.ru> +** Copyright (C) 2012 Laszlo Papp <lpapp@kde.org> +** Copyright (C) 2012 Andre Hartmann <aha_1980@gmx.de> +** Contact: http://www.qt-project.org/ +** +** This file is part of the QtSerialPort module 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 "serialport_win_p.h" + +#include <QtCore/qelapsedtimer.h> + +#include <QtCore/qthread.h> +#include <QtCore/qtimer.h> + +QT_BEGIN_NAMESPACE_SERIALPORT + +class SerialPortPrivate; + +class CommEventNotifier : public QThread +{ + Q_OBJECT +signals: + void eventMask(quint32 mask); + +public: + CommEventNotifier(DWORD mask, SerialPortPrivate *d, QObject *parent) + : QThread(parent), dptr(d), running(true) { + connect(this, SIGNAL(eventMask(quint32)), this, SLOT(processNotification(quint32))); + ::SetCommMask(dptr->descriptor, mask); + } + + virtual ~CommEventNotifier() { + running = false; + ::SetCommMask(dptr->descriptor, 0); + wait(); + } + +protected: + virtual void run() { + DWORD mask = 0; + while (running) { + if (::WaitCommEvent(dptr->descriptor, &mask, FALSE)) { + // Wait until complete the operation changes the port settings, + // see updateDcb(). + dptr->settingsChangeMutex.lock(); + dptr->settingsChangeMutex.unlock(); + emit eventMask(quint32(mask)); + } + } + } + +private slots: + void processNotification(quint32 eventMask) { + if (EV_ERR & eventMask) + dptr->processIoErrors(); + if (EV_RXCHAR &eventMask) + dptr->notifyRead(); + if (EV_TXEMPTY & eventMask) + dptr->notifyWrite(SerialPortPrivateData::WriteChunkSize); + } + +private: + SerialPortPrivate *dptr; + mutable bool running; +}; + +class WaitCommEventBreaker : public QThread +{ + Q_OBJECT +public: + WaitCommEventBreaker(HANDLE descriptor, int timeout, QObject *parent = 0) + : QThread(parent), descriptor(descriptor), timeout(timeout), worked(false) { + start(); + } + + virtual ~WaitCommEventBreaker() { + stop(); + wait(); + } + + void stop() { + exit(0); + } + + bool isWorked() const { + return worked; + } + +protected: + void run() { + QTimer timer; + QObject::connect(&timer, SIGNAL(timeout()), this, SLOT(processTimeout()), Qt::DirectConnection); + timer.start(timeout); + exec(); + worked = true; + } + +private slots: + void processTimeout() { + ::SetCommMask(descriptor, 0); + stop(); + } + +private: + HANDLE descriptor; + int timeout; + mutable bool worked; +}; + +SerialPortPrivate::SerialPortPrivate(SerialPort *q) + : SerialPortPrivateData(q) + , descriptor(INVALID_HANDLE_VALUE) + , flagErrorFromCommEvent(0) + , eventNotifier(0) +{ +} + +bool SerialPortPrivate::open(QIODevice::OpenMode mode) +{ + DWORD desiredAccess = 0; + DWORD eventMask = EV_ERR; + + if (mode & QIODevice::ReadOnly) { + desiredAccess |= GENERIC_READ; + eventMask |= EV_RXCHAR; + } + if (mode & QIODevice::WriteOnly) { + desiredAccess |= GENERIC_WRITE; + eventMask |= EV_TXEMPTY; + } + + descriptor = ::CreateFile(reinterpret_cast<const wchar_t*>(systemLocation.utf16()), + desiredAccess, 0, NULL, OPEN_EXISTING, 0, NULL); + + if (descriptor == INVALID_HANDLE_VALUE) { + portError = decodeSystemError(); + return false; + } + + if (!::GetCommState(descriptor, &restoredDcb)) { + portError = decodeSystemError(); + return false; + } + + currentDcb = restoredDcb; + currentDcb.fBinary = true; + currentDcb.fInX = false; + currentDcb.fOutX = false; + currentDcb.fAbortOnError = false; + currentDcb.fNull = false; + currentDcb.fErrorChar = false; + + if (!updateDcb()) + return false; + + if (!::GetCommTimeouts(descriptor, &restoredCommTimeouts)) { + portError = decodeSystemError(); + return false; + } + + ::memset(¤tCommTimeouts, 0, sizeof(currentCommTimeouts)); + currentCommTimeouts.ReadIntervalTimeout = MAXDWORD; + + if (!updateCommTimeouts()) + return false; + + eventNotifier = new QtAddOn::SerialPort::CommEventNotifier(eventMask, this, q_ptr); + eventNotifier->start(); + + detectDefaultSettings(); + return true; +} + +void SerialPortPrivate::close() +{ + if (eventNotifier) { + eventNotifier->deleteLater(); + eventNotifier = 0; + } + + if (restoreSettingsOnClose) { + ::SetCommState(descriptor, &restoredDcb); + ::SetCommTimeouts(descriptor, &restoredCommTimeouts); + } + + ::CloseHandle(descriptor); + descriptor = INVALID_HANDLE_VALUE; +} + +bool SerialPortPrivate::flush() +{ + return notifyWrite() && ::FlushFileBuffers(descriptor); +} + +qint64 SerialPortPrivate::readFromBuffer(char *data, qint64 maxSize) +{ + if (readBuffer.isEmpty()) + return 0; + + if (maxSize == 1) { + *data = readBuffer.getChar(); + return 1; + } + + const qint64 bytesToRead = qMin(qint64(readBuffer.size()), maxSize); + qint64 readSoFar = 0; + while (readSoFar < bytesToRead) { + const char *ptr = readBuffer.readPointer(); + const int bytesToReadFromThisBlock = qMin(int(bytesToRead - readSoFar), + readBuffer.nextDataBlockSize()); + ::memcpy(data + readSoFar, ptr, bytesToReadFromThisBlock); + readSoFar += bytesToReadFromThisBlock; + readBuffer.free(bytesToReadFromThisBlock); + } + + return readSoFar; +} + +qint64 SerialPortPrivate::writeToBuffer(const char *data, qint64 maxSize) +{ + char *ptr = writeBuffer.reserve(maxSize); + if (maxSize == 1) + *ptr = *data; + else + ::memcpy(ptr, data, maxSize); + + return maxSize; +} + +bool SerialPortPrivate::waitForReadyRead(int msec) +{ + if (!readBuffer.isEmpty()) + return true; + + QElapsedTimer stopWatch; + + stopWatch.start(); + + forever { + bool readyToRead = false; + bool readyToWrite = false; + bool timedOut = false; + if (!waitForReadOrWrite(&readyToRead, &readyToWrite, + true, !writeBuffer.isEmpty(), + timeoutValue(msec, stopWatch.elapsed()), + &timedOut)) { + return false; + } + if (readyToRead) { + if (notifyRead()) + return true; + } + if (readyToWrite) + notifyWrite(WriteChunkSize); + } + return false; +} + +bool SerialPortPrivate::waitForBytesWritten(int msec) +{ + if (writeBuffer.isEmpty()) + return false; + + QElapsedTimer stopWatch; + + stopWatch.start(); + + forever { + bool readyToRead = false; + bool readyToWrite = false; + bool timedOut = false; + if (!waitForReadOrWrite(&readyToRead, &readyToWrite, + true, !writeBuffer.isEmpty(), + timeoutValue(msec, stopWatch.elapsed()), + &timedOut)) { + return false; + } + if (readyToRead) { + if (!notifyRead()) + return false; + } + if (readyToWrite) { + if (notifyWrite(WriteChunkSize)) + return true; + } + } + return false; +} + +bool SerialPortPrivate::notifyRead() +{ + DWORD bytesToRead = (policy == SerialPort::IgnorePolicy) ? ReadChunkSize : 1; + + if (readBufferMaxSize && bytesToRead > (readBufferMaxSize - readBuffer.size())) { + bytesToRead = readBufferMaxSize - readBuffer.size(); + if (bytesToRead == 0) { + // Buffer is full. User must read data from the buffer + // before we can read more from the port. + return false; + } + } + + char *ptr = readBuffer.reserve(bytesToRead); + + DWORD readBytes = 0; + BOOL sucessResult = ::ReadFile(descriptor, ptr, bytesToRead, &readBytes, NULL); + + if (!sucessResult) { + readBuffer.truncate(bytesToRead); + return false; + } + + readBuffer.truncate(readBytes); + + // Process emulate policy. + if (flagErrorFromCommEvent) { + flagErrorFromCommEvent = false; + + switch (policy) { + case SerialPort::SkipPolicy: + readBuffer.getChar(); + return true; + case SerialPort::PassZeroPolicy: + readBuffer.getChar(); + readBuffer.putChar('\0'); + break; + case SerialPort::StopReceivingPolicy: + // FIXME: Maybe need disable read notifier? + break; + default: + break; + } + } + + if (readBytes > 0) + emit q_ptr->readyRead(); + + return true; +} + +bool SerialPortPrivate::notifyWrite(int maxSize) +{ + int nextSize = qMin(writeBuffer.nextDataBlockSize(), maxSize); + + const char *ptr = writeBuffer.readPointer(); + + DWORD bytesWritten = 0; + if (!::WriteFile(descriptor, ptr, nextSize, &bytesWritten, NULL)) + return false; + + writeBuffer.free(bytesWritten); + + if (bytesWritten > 0) + emit q_ptr->bytesWritten(bytesWritten); + + return true; +} + +bool SerialPortPrivate::waitForReadOrWrite(bool *selectForRead, bool *selectForWrite, + bool checkRead, bool checkWrite, + int msecs, bool *timedOut) +{ + // FIXME: Here the situation is not properly handled with zero timeout: + // breaker can work out before you call a method WaitCommEvent() + // and so it will loop forever! + WaitCommEventBreaker breaker(descriptor, qMax(msecs, 0)); + ::WaitCommEvent(descriptor, &eventMask, NULL); + breaker.stop(); + + if (breaker.isWorked()) + *timedOut = true; + + if (!breaker.isWorked()) { + if (checkRead) { + Q_ASSERT(selectForRead); + *selectForRead = eventMask & EV_RXCHAR; + } + if (checkWrite) { + Q_ASSERT(selectForWrite); + *selectForWrite = eventMask & EV_TXEMPTY; + } + + return true; + } + + return false; +} + +bool SerialPortPrivate::updateDcb() +{ + QMutexLocker locker(&settingsChangeMutex); + + DWORD eventMask = 0; + // Save the event mask + if (!::GetCommMask(descriptor, &eventMask)) + return false; + + // Break event notifier from WaitCommEvent + ::SetCommMask(descriptor, 0); + // Change parameters + bool ret = ::SetCommState(descriptor, ¤tDcb); + if (!ret) + portError = decodeSystemError(); + // Restore the event mask + ::SetCommMask(descriptor, eventMask); + + return ret; +} + +bool SerialPortPrivate::updateCommTimeouts() +{ + if (!::SetCommTimeouts(descriptor, ¤tCommTimeouts)) { + portError = decodeSystemError(); + return false; + } + return true; +} + +static const QLatin1String defaultPathPostfix(":"); + +QString SerialPortPrivate::portNameToSystemLocation(const QString &port) +{ + QString ret = port; + if (!ret.contains(defaultPathPostfix)) + ret.append(defaultPathPostfix); + return ret; +} + +QString SerialPortPrivate::portNameFromSystemLocation(const QString &location) +{ + QString ret = location; + if (ret.contains(defaultPathPostfix)) + ret.remove(defaultPathPostfix); + return ret; +} + +#include "serialport_wince.moc" + +QT_END_NAMESPACE_SERIALPORT diff --git a/src/serialport/serialportinfo.cpp b/src/serialport/serialportinfo.cpp new file mode 100644 index 0000000..82d927a --- /dev/null +++ b/src/serialport/serialportinfo.cpp @@ -0,0 +1,242 @@ +/**************************************************************************** +** +** Copyright (C) 2011-2012 Denis Shienkov <scapig2@yandex.ru> +** Copyright (C) 2011 Sergey Belyashov <Sergey.Belyashov@gmail.com> +** Copyright (C) 2012 Laszlo Papp <lpapp@kde.org> +** Contact: http://www.qt-project.org/ +** +** This file is part of the QtSerialPort module 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 "serialportinfo.h" +#include "serialportinfo_p.h" +#include "serialport.h" + +QT_BEGIN_NAMESPACE_SERIALPORT + + +/*! + \class SerialPortInfo + + \brief The SerialPortInfo class provides information about + existing serial ports. + + \ingroup serialport-main + \inmodule QtAddOnSerialPort + \since 5.0 + + Use the static functions to generate a list of SerialPortInfo + objects. Each SerialPortInfo object in the list represents a single + serial port and can be queried for the port name, system location, + description, and manufacturer. The SerialPortInfo class can also be + used as an input parameter for the setPort() method of the SerialPort + class. + + \sa SerialPort +*/ + +/*! + Constructs an empty SerialPortInfo object. + + \sa isNull() +*/ +SerialPortInfo::SerialPortInfo() + : d_ptr(new SerialPortInfoPrivate) +{ +} + +/*! + Constructs a copy of \a other. +*/ +SerialPortInfo::SerialPortInfo(const SerialPortInfo &other) + : d_ptr(other.d_ptr ? new SerialPortInfoPrivate(*other.d_ptr) : 0) +{ +} + +/*! + Constructs a SerialPortInfo object from serial \a port. +*/ +SerialPortInfo::SerialPortInfo(const SerialPort &port) + : d_ptr(new SerialPortInfoPrivate) +{ + foreach (const SerialPortInfo &info, availablePorts()) { + if (port.portName() == info.portName()) { + *this = info; + break; + } + } +} + +/*! + Constructs a SerialPortInfo object from serial port \a name. + + This constructor finds the relevant serial port among the available ones + according to the port name \a name, and constructs the serial port info + instance for that port. +*/ +SerialPortInfo::SerialPortInfo(const QString &name) + : d_ptr(new SerialPortInfoPrivate) +{ + foreach (const SerialPortInfo &info, availablePorts()) { + if (name == info.portName()) { + *this = info; + break; + } + } +} + +/*! + Destroys the SerialPortInfo object. References to the values in the + object become invalid. +*/ +SerialPortInfo::~SerialPortInfo() +{ +} + +/*! \fn void SerialPortInfo::swap(SerialPortInfo &other) + + Swaps SerialPortInfo \a other with this SerialPortInfo. This operation is + very fast and never fails. +*/ +void SerialPortInfo::swap(SerialPortInfo &other) +{ + d_ptr.swap(other.d_ptr); +} + +/*! + Sets the SerialPortInfo object to be equal to \a other. +*/ +SerialPortInfo& SerialPortInfo::operator=(const SerialPortInfo &other) +{ + SerialPortInfo(other).swap(*this); + return *this; +} + +/*! + Returns the name of the serial port. +*/ +QString SerialPortInfo::portName() const +{ + Q_D(const SerialPortInfo); + return !d ? QString() : d->portName; +} + +/*! + Returns the system location of the serial port. +*/ +QString SerialPortInfo::systemLocation() const +{ + Q_D(const SerialPortInfo); + return !d ? QString() : d->device; +} + +/*! + Returns the description string of the serial port, + if available; otherwise returns an empty string. +*/ +QString SerialPortInfo::description() const +{ + Q_D(const SerialPortInfo); + return !d ? QString() : d->description; +} + +/*! + Returns the manufacturer string of the serial port, + if available; otherwise returns an empty string. +*/ +QString SerialPortInfo::manufacturer() const +{ + Q_D(const SerialPortInfo); + return !d ? QString() : d->manufacturer; +} + +/*! + Returns the vendor identifier string of the serial + port in hexadecimal format, if available; otherwise + returns an empty string. +*/ +QString SerialPortInfo::vendorIdentifier() const +{ + Q_D(const SerialPortInfo); + return !d ? QString() : d->vendorIdentifier; +} + +/*! + Returns the product identifier string of the serial + port in hexadecimal format, if available; otherwise + returns an empty string. +*/ +QString SerialPortInfo::productIdentifier() const +{ + Q_D(const SerialPortInfo); + return !d ? QString() : d->productIdentifier; +} + +/*! + \fn bool SerialPortInfo::isNull() const + + Returns whether this SerialPortInfo object holds a + serial port definition. +*/ + +/*! + \fn bool SerialPortInfo::isBusy() const + + Returns true if serial port is busy; + otherwise returns false. +*/ + +/*! + \fn bool SerialPortInfo::isValid() const + + Returns true if serial port is present on system; + otherwise returns false. +*/ + +/*! + \fn QList<qint32> SerialPortInfo::standardRates() + + Returns a list of available standard baud rates supported by + the current serial port. +*/ + +/*! + \fn QList<SerialPortInfo> SerialPortInfo::availablePorts() + + Returns a list of available serial ports on the system. +*/ + +QT_END_NAMESPACE_SERIALPORT diff --git a/src/serialport/serialportinfo.h b/src/serialport/serialportinfo.h new file mode 100644 index 0000000..049a468 --- /dev/null +++ b/src/serialport/serialportinfo.h @@ -0,0 +1,93 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Denis Shienkov <scapig@yandex.ru> +** Copyright (C) 2012 Laszlo Papp <lpapp@kde.org> +** Contact: http://www.qt-project.org/ +** +** This file is part of the QtSerialPort module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this +** file. Please review the following information to ensure the GNU Lesser +** General Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef SERIALPORTINFO_H +#define SERIALPORTINFO_H + +#include <QtCore/qlist.h> +#include <QtCore/qscopedpointer.h> + +#include "serialport-global.h" + +QT_BEGIN_NAMESPACE_SERIALPORT + +class SerialPort; +class SerialPortInfoPrivate; +class SerialInfoPrivateDeleter; + +class Q_SERIALPORT_EXPORT SerialPortInfo +{ + Q_DECLARE_PRIVATE(SerialPortInfo) +public: + SerialPortInfo(); + SerialPortInfo(const SerialPortInfo &other); + SerialPortInfo(const SerialPort &port); + SerialPortInfo(const QString &name); + ~SerialPortInfo(); + + SerialPortInfo& operator=(const SerialPortInfo &other); + void swap(SerialPortInfo &other); + + QString portName() const; + QString systemLocation() const; + QString description() const; + QString manufacturer() const; + QString vendorIdentifier() const; + QString productIdentifier() const; + + bool isNull() const; + bool isBusy() const; + bool isValid() const; + + static QList<qint32> standardRates(); + static QList<SerialPortInfo> availablePorts(); + +private: + QScopedPointer<SerialPortInfoPrivate, SerialInfoPrivateDeleter> d_ptr; +}; + +inline bool SerialPortInfo::isNull() const +{ return !d_ptr; } + +QT_END_NAMESPACE_SERIALPORT + +#endif // SERIALPORTINFO_H diff --git a/src/serialport/serialportinfo_mac.cpp b/src/serialport/serialportinfo_mac.cpp new file mode 100644 index 0000000..6672a84 --- /dev/null +++ b/src/serialport/serialportinfo_mac.cpp @@ -0,0 +1,264 @@ +/**************************************************************************** +** +** Copyright (C) 2011-2012 Denis Shienkov <scapig2@yandex.ru> +** Copyright (C) 2011 Sergey Belyashov <Sergey.Belyashov@gmail.com> +** Copyright (C) 2012 Laszlo Papp <lpapp@kde.org> +** Contact: http://www.qt-project.org/ +** +** This file is part of the QtSerialPort module 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 "serialportinfo.h" +#include "serialportinfo_p.h" + +#include <sys/param.h> + +#include <CoreFoundation/CoreFoundation.h> +#include <IOKit/IOKitLib.h> + +#include <IOKit/serial/IOSerialKeys.h> +#include <IOKit/storage/IOStorageDeviceCharacteristics.h> // for kIOPropertyProductNameKey +#include <IOKit/usb/USB.h> +#if defined(MAC_OS_X_VERSION_10_4) && (MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_4) +# include <IOKit/serial/ioss.h> +#endif +#include <IOKit/IOBSD.h> + +QT_BEGIN_NAMESPACE_SERIALPORT + +enum { MATCHING_PROPERTIES_COUNT = 6 }; + +QList<SerialPortInfo> SerialPortInfo::availablePorts() +{ + QList<SerialPortInfo> ports; + + int matchingPropertiesCounter = 0; + + + ::CFMutableDictionaryRef matching = ::IOServiceMatching(kIOSerialBSDServiceValue); + + ::CFDictionaryAddValue(matching, + CFSTR(kIOSerialBSDTypeKey), + CFSTR(kIOSerialBSDAllTypes)); + + io_iterator_t iter = 0; + kern_return_t kr = ::IOServiceGetMatchingServices(kIOMasterPortDefault, + matching, + &iter); + + if (kr != kIOReturnSuccess) + return ports; + + io_registry_entry_t service; + + while ((service = ::IOIteratorNext(iter))) { + + ::CFTypeRef device = 0; + ::CFTypeRef portName = 0; + ::CFTypeRef description = 0; + ::CFTypeRef manufacturer = 0; + ::CFTypeRef vendorIdentifier = 0; + ::CFTypeRef productIdentifier = 0; + + io_registry_entry_t entry = service; + + // Find MacOSX-specific properties names. + do { + + if (!device) { + device = + ::IORegistryEntrySearchCFProperty(entry, + kIOServicePlane, + CFSTR(kIOCalloutDeviceKey), + kCFAllocatorDefault, + 0); + if (device) + ++matchingPropertiesCounter; + } + + if (!portName) { + portName = + ::IORegistryEntrySearchCFProperty(entry, + kIOServicePlane, + CFSTR(kIOTTYDeviceKey), + kCFAllocatorDefault, + 0); + if (portName) + ++matchingPropertiesCounter; + } + + if (!description) { + description = + ::IORegistryEntrySearchCFProperty(entry, + kIOServicePlane, + CFSTR(kIOPropertyProductNameKey), + kCFAllocatorDefault, + 0); + if (!description) + description = + ::IORegistryEntrySearchCFProperty(entry, + kIOServicePlane, + CFSTR(kUSBProductString), + kCFAllocatorDefault, + 0); + if (description) + ++matchingPropertiesCounter; + } + + if (!manufacturer) { + manufacturer = + ::IORegistryEntrySearchCFProperty(entry, + kIOServicePlane, + CFSTR(kUSBVendorString), + kCFAllocatorDefault, + 0); + if (manufacturer) + ++matchingPropertiesCounter; + + } + + if (!vendorIdentifier) { + vendorIdentifier = + ::IORegistryEntrySearchCFProperty(entry, + kIOServicePlane, + CFSTR(kUSBVendorID), + kCFAllocatorDefault, + 0); + if (vendorIdentifier) + ++matchingPropertiesCounter; + + } + + if (!productIdentifier) { + productIdentifier = + ::IORegistryEntrySearchCFProperty(entry, + kIOServicePlane, + CFSTR(kUSBProductID), + kCFAllocatorDefault, + 0); + if (productIdentifier) + ++matchingPropertiesCounter; + + } + + // If all matching properties is found, then force break loop. + if (matchingPropertiesCounter == MATCHING_PROPERTIES_COUNT) + break; + + kr = ::IORegistryEntryGetParentEntry(entry, kIOServicePlane, &entry); + + } while (kr == kIOReturnSuccess); + + (void) ::IOObjectRelease(entry); + + // Convert from MacOSX-specific properties to Qt4-specific. + if (matchingPropertiesCounter > 0) { + + SerialPortInfo info; + QByteArray buffer(MAXPATHLEN, 0); + + if (device) { + if (::CFStringGetCString(CFStringRef(device), + buffer.data(), + buffer.size(), + kCFStringEncodingUTF8)) { + info.d_ptr->device = QString(buffer); + } + ::CFRelease(device); + } + + if (portName) { + if (::CFStringGetCString(CFStringRef(portName), + buffer.data(), + buffer.size(), + kCFStringEncodingUTF8)) { + info.d_ptr->portName = QString(buffer); + } + ::CFRelease(portName); + } + + if (description) { + ::CFStringGetCString(CFStringRef(description), + buffer.data(), + buffer.size(), + kCFStringEncodingUTF8); + + info.d_ptr->description = QString(buffer); + ::CFRelease(description); + } + + if (manufacturer) { + ::CFStringGetCString(CFStringRef(manufacturer), + buffer.data(), + buffer.size(), + kCFStringEncodingUTF8); + + info.d_ptr->manufacturer = QString(buffer); + ::CFRelease(manufacturer); + } + + int value = 0; + + if (vendorIdentifier) { + ::CFNumberGetValue(CFNumberRef(vendorIdentifier), + kCFNumberIntType, + &value); + + info.d_ptr->vendorIdentifier = QString::number(value, 16); + ::CFRelease(vendorIdentifier); + } + + if (productIdentifier) { + ::CFNumberGetValue(CFNumberRef(productIdentifier), + kCFNumberIntType, + &value); + + info.d_ptr->productIdentifier = QString::number(value, 16); + ::CFRelease(productIdentifier); + } + + ports.append(info); + } + + (void) ::IOObjectRelease(service); + } + + (void) ::IOObjectRelease(iter); + + return ports; +} + +QT_END_NAMESPACE_SERIALPORT diff --git a/src/serialport/serialportinfo_p.h b/src/serialport/serialportinfo_p.h new file mode 100644 index 0000000..262efa8 --- /dev/null +++ b/src/serialport/serialportinfo_p.h @@ -0,0 +1,77 @@ +/**************************************************************************** +** +** Copyright (C) 2011-2012 Denis Shienkov <scapig2@yandex.ru> +** Copyright (C) 2011 Sergey Belyashov <Sergey.Belyashov@gmail.com> +** Copyright (C) 2012 Laszlo Papp <lpapp@kde.org> +** Contact: http://www.qt-project.org/ +** +** This file is part of the QtSerialPort module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this +** file. Please review the following information to ensure the GNU Lesser +** General Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef SERIALPORTINFO_P_H +#define SERIALPORTINFO_P_H + +#include "serialport-global.h" + +#include <QtCore/qstring.h> + +QT_BEGIN_NAMESPACE_SERIALPORT + +class SerialPortInfoPrivate +{ +public: + SerialPortInfoPrivate() {} + ~SerialPortInfoPrivate() {} + + QString portName; + QString device; + QString description; + QString manufacturer; + QString vendorIdentifier; + QString productIdentifier; +}; + +class SerialInfoPrivateDeleter +{ +public: + static void cleanup(SerialPortInfoPrivate *p) { + delete p; + } +}; + +QT_END_NAMESPACE_SERIALPORT + +#endif // SERIALPORTINFO_P_H diff --git a/src/serialport/serialportinfo_symbian.cpp b/src/serialport/serialportinfo_symbian.cpp new file mode 100644 index 0000000..057c760 --- /dev/null +++ b/src/serialport/serialportinfo_symbian.cpp @@ -0,0 +1,253 @@ +/**************************************************************************** +** +** Copyright (C) 2011-2012 Denis Shienkov <scapig2@yandex.ru> +** Copyright (C) 2011 Sergey Belyashov <Sergey.Belyashov@gmail.com> +** Copyright (C) 2012 Laszlo Papp <lpapp@kde.org> +** Contact: http://www.qt-project.org/ +** +** This file is part of the QtSerialPort module 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 "serialportinfo.h" +#include "serialportinfo_p.h" +#include "serialport_symbian_p.h" + +#include <e32base.h> +//#include <e32test.h> +#include <c32comm.h> +#include <f32file.h> + +#include <QtCore/qobject.h> + +QT_BEGIN_NAMESPACE_SERIALPORT + +// Physical device driver. +#ifdef __WINS__ +_LIT(KPddName, "ECDRV"); +#else // defined (__EPOC32__) +_LIT(KPddName, "EUART"); +#endif + +// Logical native device driver. +_LIT(KLddName,"ECOMM"); + +// Modules names. +_LIT(KRS232ModuleName , "ECUART"); +_LIT(KBluetoothModuleName , "BTCOMM"); +_LIT(KInfraRedModuleName , "IRCOMM"); +_LIT(KACMModuleName, "ECACM"); + +// Return false on error load. +static bool loadDevices() +{ + TInt r = KErrNone; +#ifdef __WINS__ + RFs fileServer; + r = User::LeaveIfError(fileServer.Connect()); + if (r != KErrNone) + return false; + fileServer.Close (); +#endif + + r = User::LoadPhysicalDevice(KPddName); + if (r != KErrNone && r != KErrAlreadyExists) + return false; //User::Leave(r); + + r = User::LoadLogicalDevice(KLddName); + if (r != KErrNone && r != KErrAlreadyExists) + return false; //User::Leave(r); + +#ifndef __WINS__ + r = StartC32(); + if (r != KErrNone && r != KErrAlreadyExists) + return false; //User::Leave(r); +#endif + + return true; +} + +QList<SerialPortInfo> SerialPortInfo::availablePorts() +{ + QList<SerialPortInfo> ports; + + if (!loadDevices()) + return ports; + + RCommServ server; + TInt r = server.Connect(); + if (r != KErrNone) + return ports; //User::LeaveIfError(r); + + TSerialInfo nativeInfo; // Native Symbian OS port info class. + QString s("%1::%2"); + + // FIXME: Get info about RS232 ports. + r = server.LoadCommModule(KRS232ModuleName); + //User::LeaveIfError(r); + if (r == KErrNone) { + r = server.GetPortInfo(KRS232ModuleName, nativeInfo); + if (r == KErrNone) { + // + for (quint32 i = nativeInfo.iLowUnit; i < nativeInfo.iHighUnit + 1; ++i) { + + SerialPortInfo info; // My (desired) info class. + + info.d_ptr->device = s + .arg(QString::fromUtf16(nativeInfo.iName.Ptr(), nativeInfo.iName.Length())) + .arg(i); + info.d_ptr->portName = info.d_ptr->device; + info.d_ptr->description = + QString::fromUtf16(nativeInfo.iDescription.Ptr(), nativeInfo.iDescription.Length()); + info.d_ptr->manufacturer = QString(QObject::tr("Unknown.")); + ports.append(info); + } + } + } + + // FIXME: Get info about Bluetooth ports. + r = server.LoadCommModule(KBluetoothModuleName); + //User::LeaveIfError(r); + if (r == KErrNone) { + r = server.GetPortInfo(KBluetoothModuleName, nativeInfo); + if (r == KErrNone) { + // + for (quint32 i = nativeInfo.iLowUnit; i < nativeInfo.iHighUnit + 1; ++i) { + + SerialPortInfo info; // My (desired) info class. + + info.d_ptr->device = s + .arg(QString::fromUtf16(nativeInfo.iName.Ptr(), nativeInfo.iName.Length())) + .arg(i); + info.d_ptr->portName = info.d_ptr->device; + info.d_ptr->description = + QString::fromUtf16(nativeInfo.iDescription.Ptr(), nativeInfo.iDescription.Length()); + info.d_ptr->manufacturer = QString(QObject::tr("Unknown.")); + ports.append(info); + } + } + } + + // FIXME: Get info about InfraRed ports. + r = server.LoadCommModule(KInfraRedModuleName); + //User::LeaveIfError(r); + if (r == KErrNone) { + r = server.GetPortInfo(KInfraRedModuleName, nativeInfo); + if (r == KErrNone) { + // + for (quint32 i = nativeInfo.iLowUnit; i < nativeInfo.iHighUnit + 1; ++i) { + + SerialPortInfo info; // My (desired) info class. + + info.d_ptr->device = s + .arg(QString::fromUtf16(nativeInfo.iName.Ptr(), nativeInfo.iName.Length())) + .arg(i); + info.d_ptr->portName = info.d_ptr->device; + info.d_ptr->description = + QString::fromUtf16(nativeInfo.iDescription.Ptr(), nativeInfo.iDescription.Length()); + info.d_ptr->manufacturer = QString(QObject::tr("Unknown.")); + ports.append(info); + } + } + } + + // FIXME: Get info about ACM ports. + r = server.LoadCommModule(KACMModuleName); + //User::LeaveIfError(r); + if (r == KErrNone) { + r = server.GetPortInfo(KACMModuleName, nativeInfo); + if (r == KErrNone) { + // + for (quint32 i = nativeInfo.iLowUnit; i < nativeInfo.iHighUnit + 1; ++i) { + + SerialPortInfo info; // My (desired) info class. + + info.d_ptr->device = s + .arg(QString::fromUtf16(nativeInfo.iName.Ptr(), nativeInfo.iName.Length())) + .arg(i); + info.d_ptr->portName = SerialPortPrivate::portNameFromSystemLocation(info.d_ptr->device); + info.d_ptr->description = + QString::fromUtf16(nativeInfo.iDescription.Ptr(), nativeInfo.iDescription.Length()); + info.d_ptr->manufacturer = QString(QObject::tr("Unknown.")); + ports.append(info); + } + } + } + + return ports; +} + +QList<qint32> SerialPortInfo::standardRates() +{ + return SerialPortPrivate::standardRates(); +} + +bool SerialPortInfo::isBusy() const +{ + if (!loadDevices()) + return false; + + RCommServ server; + TInt r = server.Connect(); + if (r != KErrNone) + return false; + + RComm port; + TPtrC portName(static_cast<const TUint16*>(systemLocation().utf16()), systemLocation().length()); + r = port.Open(server, portName, ECommExclusive); + if (r == KErrNone) + port.Close(); + return r == KErrLocked; +} + +bool SerialPortInfo::isValid() const +{ + if (!loadDevices()) + return false; + + RCommServ server; + TInt r = server.Connect(); + if (r != KErrNone) + return false; + + RComm port; + TPtrC portName(static_cast<const TUint16*>(systemLocation().utf16()), systemLocation().length()); + r = port.Open(server, portName, ECommExclusive); + if (r == KErrNone) + port.Close(); + return r == KErrNone || r == KErrLocked; +} + +QT_END_NAMESPACE_SERIALPORT diff --git a/src/serialport/serialportinfo_unix.cpp b/src/serialport/serialportinfo_unix.cpp new file mode 100644 index 0000000..2869d44 --- /dev/null +++ b/src/serialport/serialportinfo_unix.cpp @@ -0,0 +1,264 @@ +/**************************************************************************** +** +** Copyright (C) 2011-2012 Denis Shienkov <scapig2@yandex.ru> +** Copyright (C) 2011 Sergey Belyashov <Sergey.Belyashov@gmail.com> +** Copyright (C) 2012 Laszlo Papp <lpapp@kde.org> +** Contact: http://www.qt-project.org/ +** +** This file is part of the QtSerialPort module 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 "serialportinfo.h" +#include "serialportinfo_p.h" +#include "ttylocker_unix_p.h" +#include "serialport_unix_p.h" +#include <QtCore/qfile.h> + +#ifndef Q_OS_MAC + +#if defined (Q_OS_LINUX) && defined (HAVE_LIBUDEV) +extern "C" +{ +#include <libudev.h> +} +#else +#include <QtCore/qdir.h> +#include <QtCore/qstringlist.h> +#endif + +#endif // Q_OS_MAC + +QT_BEGIN_NAMESPACE_SERIALPORT + +#ifndef Q_OS_MAC + +#if defined (Q_OS_LINUX) && defined (HAVE_LIBUDEV) + +// White list for devices without a parent +static const QString rfcommDeviceName(QLatin1String("rfcomm")); + +#else + +static QStringList generateFiltersOfDevices() +{ + QStringList l; + +# ifdef Q_OS_LINUX + l << QLatin1String("ttyS*") // Standart UART 8250 and etc. + << QLatin1String("ttyUSB*") // Usb/serial converters PL2303 and etc. + << QLatin1String("ttyACM*") // CDC_ACM converters (i.e. Mobile Phones). + << QLatin1String("ttyGS*") // Gadget serial device (i.e. Mobile Phones with gadget serial driver). + << QLatin1String("ttyMI*") // MOXA pci/serial converters. + << QLatin1String("rfcomm*"); // Bluetooth serial device. +# elif defined (Q_OS_FREEBSD) + l << QLatin1String("cu*"); +# else + // Here for other *nix OS. +# endif + + return l; +} + +inline QStringList& filtersOfDevices() +{ + static QStringList l = generateFiltersOfDevices(); + return l; +} + +#endif + +QList<SerialPortInfo> SerialPortInfo::availablePorts() +{ + QList<SerialPortInfo> ports; + +#if defined (Q_OS_LINUX) && defined (HAVE_LIBUDEV) + + struct ::udev *udev = ::udev_new(); + if (udev) { + + struct ::udev_enumerate *enumerate = + ::udev_enumerate_new(udev); + + if (enumerate) { + + ::udev_enumerate_add_match_subsystem(enumerate, "tty"); + ::udev_enumerate_scan_devices(enumerate); + + struct ::udev_list_entry *devices = + ::udev_enumerate_get_list_entry(enumerate); + + struct ::udev_list_entry *dev_list_entry; + udev_list_entry_foreach(dev_list_entry, devices) { + + struct ::udev_device *dev = + ::udev_device_new_from_syspath(udev, + ::udev_list_entry_get_name(dev_list_entry)); + + if (dev) { + + SerialPortInfo info; + + info.d_ptr->device = + QLatin1String(::udev_device_get_devnode(dev)); + info.d_ptr->portName = + QLatin1String(::udev_device_get_sysname(dev)); + + struct ::udev_device *parentdev = ::udev_device_get_parent(dev); + + bool canAppendToList = true; + + if (parentdev) { + + QLatin1String subsys(::udev_device_get_subsystem(parentdev)); + + if (subsys == QLatin1String("usb-serial") + || subsys == QLatin1String("usb")) { // USB bus type + // Append this devices and try get additional information about them. + info.d_ptr->description = QString( + QLatin1String(::udev_device_get_property_value(dev, + "ID_MODEL"))).replace('_', ' '); + info.d_ptr->manufacturer = QString( + QLatin1String(::udev_device_get_property_value(dev, + "ID_VENDOR"))).replace('_', ' '); + info.d_ptr->vendorIdentifier = + QLatin1String(::udev_device_get_property_value(dev, + "ID_VENDOR_ID")); + info.d_ptr->productIdentifier = + QLatin1String(::udev_device_get_property_value(dev, + "ID_MODEL_ID")); + } else if (subsys == QLatin1String("pnp")) { // PNP bus type + // Append this device. + // FIXME: How to get additional information about serial devices + // with this subsystem? + } else if (subsys == QLatin1String("platform")) { // Platform 'pseudo' bus for legacy device. + // Skip this devices because this type of subsystem does + // not include a real physical serial device. + canAppendToList = false; + } else { // Others types of subsystems. + // Append this devices because we believe that any other types of + // subsystems provide a real serial devices. For example, for devices + // such as ttyGSx, its driver provide an empty subsystem name, but it + // devices is a real physical serial devices. + // FIXME: How to get additional information about serial devices + // with this subsystems? + } + } else { // Devices without a parent + if (info.d_ptr->portName.startsWith(rfcommDeviceName)) { // Bluetooth device + bool ok; + // Check for an unsigned decimal integer at the end of the device name: "rfcomm0", "rfcomm15" + // devices with negative and invalid numbers in the name are rejected + int portNumber = info.d_ptr->portName.mid(rfcommDeviceName.length()).toInt(&ok); + + if (!ok || (portNumber < 0) || (portNumber > 255)) { + canAppendToList = false; + } + } else { + canAppendToList = false; + } + } + + if (canAppendToList) + ports.append(info); + + ::udev_device_unref(dev); + } + + } + + ::udev_enumerate_unref(enumerate); + } + + ::udev_unref(udev); + } + +#elif defined (Q_OS_FREEBSD) && defined (HAVE_LIBUSB) + // TODO: Implement me. +#else + + QDir devDir(QLatin1String("/dev")); + if (devDir.exists()) { + + devDir.setNameFilters(filtersOfDevices()); + devDir.setFilter(QDir::Files | QDir::System | QDir::NoSymLinks); + + QStringList foundDevices; // Found devices list. + + foreach (const QFileInfo &fi, devDir.entryInfoList()) { + QString s = fi.absoluteFilePath(); + if (!foundDevices.contains(s)) { + foundDevices.append(s); + + SerialPortInfo info; + + info.d_ptr->device = s; + info.d_ptr->portName = SerialPortPrivate::portNameFromSystemLocation(s); + + // Get description, manufacturer, vendor identifier, product + // identifier are not supported. + + ports.append(info); + + } + } + } + +#endif + + return ports; +} + +#endif // Q_OS_MAC + +// common part + +QList<qint32> SerialPortInfo::standardRates() +{ + return SerialPortPrivate::standardRates(); +} + +bool SerialPortInfo::isBusy() const +{ + bool currentPid = false; + return TtyLocker::isLocked(portName().toLocal8Bit().constData(), ¤tPid); +} + +bool SerialPortInfo::isValid() const +{ + QFile f(systemLocation()); + return f.exists(); +} + +QT_END_NAMESPACE_SERIALPORT diff --git a/src/serialport/serialportinfo_win.cpp b/src/serialport/serialportinfo_win.cpp new file mode 100644 index 0000000..a0e4c73 --- /dev/null +++ b/src/serialport/serialportinfo_win.cpp @@ -0,0 +1,222 @@ +/**************************************************************************** +** +** Copyright (C) 2011-2012 Denis Shienkov <scapig2@yandex.ru> +** Copyright (C) 2011 Sergey Belyashov <Sergey.Belyashov@gmail.com> +** Copyright (C) 2012 Laszlo Papp <lpapp@kde.org> +** Contact: http://www.qt-project.org/ +** +** This file is part of the QtSerialPort module 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 "serialportinfo.h" +#include "serialportinfo_p.h" +#include "serialport_win_p.h" + +#include <qt_windows.h> + +#ifndef Q_OS_WINCE +#include <initguid.h> +#include <setupapi.h> +#endif + +#include <QtCore/qvariant.h> +#include <QtCore/qstringlist.h> + +QT_BEGIN_NAMESPACE_SERIALPORT + +#ifndef Q_OS_WINCE + +static const GUID guidsArray[] = +{ + // Windows Ports Class GUID + { 0x4D36E978, 0xE325, 0x11CE, { 0xBF, 0xC1, 0x08, 0x00, 0x2B, 0xE1, 0x03, 0x18 } }, + // Virtual Ports Class GUID (i.e. com0com and etc) + { 0xDF799E12, 0x3C56, 0x421B, { 0xB2, 0x98, 0xB6, 0xD3, 0x64, 0x2B, 0xC8, 0x78 } }, + // Windows Modems Class GUID + { 0x4D36E96D, 0xE325, 0x11CE, { 0xBF, 0xC1, 0x08, 0x00, 0x2B, 0xE1, 0x03, 0x18 } }, + // Eltima Virtual Serial Port Driver v4 GUID + { 0xCC0EF009, 0xB820, 0x42F4, { 0x95, 0xA9, 0x9B, 0xFA, 0x6A, 0x5A, 0xB7, 0xAB } }, + // Advanced Virtual COM Port GUID + { 0x9341CD95, 0x4371, 0x4A37, { 0xA5, 0xAF, 0xFD, 0xB0, 0xA9, 0xD1, 0x96, 0x31 } }, +}; + +static const wchar_t portKeyName[] = L"PortName"; + +static QVariant deviceRegistryProperty(HDEVINFO deviceInfoSet, + PSP_DEVINFO_DATA deviceInfoData, + DWORD property) +{ + DWORD dataType = 0; + DWORD dataSize = 0; + ::SetupDiGetDeviceRegistryProperty(deviceInfoSet, deviceInfoData, + property, &dataType, NULL, 0, &dataSize); + QByteArray data(dataSize, 0); + if (!::SetupDiGetDeviceRegistryProperty(deviceInfoSet, deviceInfoData, property, NULL, + reinterpret_cast<unsigned char*>(data.data()), + dataSize, NULL) + || !dataSize) { + return QVariant(); + } + + switch (dataType) { + + case REG_EXPAND_SZ: + case REG_SZ: { + return QVariant(QString::fromWCharArray(reinterpret_cast<const wchar_t *>(data.constData()))); + } + + case REG_MULTI_SZ: { + QStringList list; + int i = 0; + forever { + QString s = QString::fromWCharArray(reinterpret_cast<const wchar_t *>(data.constData()) + i); + i += s.length() + 1; + if (s.isEmpty()) + break; + list.append(s); + } + return QVariant(list); + } + + default: + break; + } + + return QVariant(); +} + +static QString devicePortName(HDEVINFO deviceInfoSet, PSP_DEVINFO_DATA deviceInfoData) +{ + const HKEY key = ::SetupDiOpenDevRegKey(deviceInfoSet, deviceInfoData, DICS_FLAG_GLOBAL, + 0, DIREG_DEV, KEY_READ); + if (key == INVALID_HANDLE_VALUE) + return QString(); + + DWORD dataSize; + if (::RegQueryValueEx(key, portKeyName, NULL, NULL, NULL, &dataSize) != ERROR_SUCCESS) { + ::RegCloseKey(key); + return QString(); + } + + QByteArray data(dataSize, 0); + + if (::RegQueryValueEx(key, portKeyName, NULL, NULL, + reinterpret_cast<unsigned char *>(data.data()), &dataSize) != ERROR_SUCCESS) { + ::RegCloseKey(key); + return QString(); + } + ::RegCloseKey(key); + return QString::fromWCharArray(((const wchar_t *)data.constData())); +} + +QList<SerialPortInfo> SerialPortInfo::availablePorts() +{ + QList<SerialPortInfo> ports; + static const int guidCount = sizeof(guidsArray)/sizeof(guidsArray[0]); + + for (int i = 0; i < guidCount; ++i) { + const HDEVINFO deviceInfoSet = ::SetupDiGetClassDevs(&guidsArray[i], NULL, 0, DIGCF_PRESENT); + if (deviceInfoSet == INVALID_HANDLE_VALUE) + return ports; + + SP_DEVINFO_DATA deviceInfoData; + ::memset(&deviceInfoData, 0, sizeof(deviceInfoData)); + deviceInfoData.cbSize = sizeof(deviceInfoData); + + DWORD index = 0; + while (::SetupDiEnumDeviceInfo(deviceInfoSet, index++, &deviceInfoData)) { + SerialPortInfo info; + + QString s = devicePortName(deviceInfoSet, &deviceInfoData); + if (s.isEmpty() || s.contains(QLatin1String("LPT"))) + continue; + + info.d_ptr->portName = s; + info.d_ptr->device = SerialPortPrivate::portNameToSystemLocation(s); + info.d_ptr->description = + deviceRegistryProperty(deviceInfoSet, &deviceInfoData, SPDRP_DEVICEDESC).toString(); + info.d_ptr->manufacturer = + deviceRegistryProperty(deviceInfoSet, &deviceInfoData, SPDRP_MFG).toString(); + + s = deviceRegistryProperty(deviceInfoSet, &deviceInfoData, SPDRP_HARDWAREID).toStringList().at(0).toUpper(); + info.d_ptr->vendorIdentifier = s.mid(s.indexOf(QLatin1String("VID_")) + 4, 4); + info.d_ptr->productIdentifier = s.mid(s.indexOf(QLatin1String("PID_")) + 4, 4); + + ports.append(info); + } + ::SetupDiDestroyDeviceInfoList(deviceInfoSet); + } + return ports; +} + +#endif + +// common part + +QList<qint32> SerialPortInfo::standardRates() +{ + return SerialPortPrivate::standardRates(); +} + +bool SerialPortInfo::isBusy() const +{ + const HANDLE descriptor = ::CreateFile(reinterpret_cast<const wchar_t*>(systemLocation().utf16()), + GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL); + + if (descriptor == INVALID_HANDLE_VALUE) { + if (::GetLastError() == ERROR_ACCESS_DENIED) + return true; + } else { + ::CloseHandle(descriptor); + } + return false; +} + +bool SerialPortInfo::isValid() const +{ + const HANDLE descriptor = ::CreateFile(reinterpret_cast<const wchar_t*>(systemLocation().utf16()), + GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL); + + if (descriptor == INVALID_HANDLE_VALUE) { + if (::GetLastError() != ERROR_ACCESS_DENIED) + return false; + } else { + ::CloseHandle(descriptor); + } + return true; +} + +QT_END_NAMESPACE_SERIALPORT diff --git a/src/serialport/serialportinfo_wince.cpp b/src/serialport/serialportinfo_wince.cpp new file mode 100644 index 0000000..1fbbd86 --- /dev/null +++ b/src/serialport/serialportinfo_wince.cpp @@ -0,0 +1,136 @@ +/**************************************************************************** +** +** Copyright (C) 2011-2012 Denis Shienkov <scapig2@yandex.ru> +** Copyright (C) 2011 Sergey Belyashov <Sergey.Belyashov@gmail.com> +** Copyright (C) 2012 Laszlo Papp <lpapp@kde.org> +** Contact: http://www.qt-project.org/ +** +** This file is part of the QtSerialPort module 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 "serialportinfo.h" +#include "serialportinfo_p.h" +#include "serialport_win_p.h" + +#include <qt_windows.h> + +#include <QtCore/qvariant.h> +#include <QtCore/qstringlist.h> + +QT_BEGIN_NAMESPACE_SERIALPORT + +const static QString valueName(QLatin1String("FriendlyName")); +static QString findDescription(HKEY parentKeyHandle, const QString &subKey) +{ + QString result; + HKEY hSubKey = 0; + LONG res = ::RegOpenKeyEx(parentKeyHandle, reinterpret_cast<const wchar_t *>(subKey.utf16()), + 0, KEY_QUERY_VALUE | KEY_ENUMERATE_SUB_KEYS, &hSubKey); + + if (res == ERROR_SUCCESS) { + + DWORD dataType = 0; + DWORD dataSize = 0; + res = ::RegQueryValueEx(hSubKey, reinterpret_cast<const wchar_t *>(valueName.utf16()), + NULL, &dataType, NULL, &dataSize); + + if (res == ERROR_SUCCESS) { + QByteArray data(dataSize, 0); + res = ::RegQueryValueEx(hSubKey, reinterpret_cast<const wchar_t *>(valueName.utf16()), + NULL, NULL, + reinterpret_cast<unsigned char *>(data.data()), + &dataSize); + + if (res == ERROR_SUCCESS) { + switch (dataType) { + case REG_EXPAND_SZ: + case REG_SZ: + if (dataSize) + result = QString::fromWCharArray(reinterpret_cast<const wchar_t *>(data.constData())); + break; + default: + break; + } + } + } else { + DWORD index = 0; + dataSize = 255; // Max. key length (see MSDN). + QByteArray data(dataSize, 0); + while (::RegEnumKeyEx(hSubKey, index++, + reinterpret_cast<wchar_t *>(data.data()), &dataSize, + NULL, NULL, NULL, NULL) == ERROR_SUCCESS) { + + result = findDescription(hSubKey, + QString::fromUtf16(reinterpret_cast<ushort *>(data.data()), dataSize)); + if (!result.isEmpty()) + break; + } + } + ::RegCloseKey(hSubKey); + } + return result; +} + +QList<SerialPortInfo> SerialPortInfo::availablePorts() +{ + QList<SerialPortInfo> ports; + + DEVMGR_DEVICE_INFORMATION di; + di.dwSize = sizeof(di); + const HANDLE hSearch = ::FindFirstDevice(DeviceSearchByLegacyName, + L"COM*", + &di); + if (hSearch != INVALID_HANDLE_VALUE) { + do { + SerialPortInfo info; + info.d_ptr->device = QString::fromWCharArray(di.szLegacyName); + info.d_ptr->portName = SerialPortPrivate::portNameFromSystemLocation(info.d_ptr->device); + info.d_ptr->description = findDescription(HKEY_LOCAL_MACHINE, + QString::fromWCharArray(di.szDeviceKey)); + + // Get manufacturer, vendor identifier, product identifier are not + // possible. + + ports.append(info); + + } while (::FindNextDevice(hSearch, &di)); + ::FindClose(hSearch); + } + + return ports; +} + +QT_END_NAMESPACE_SERIALPORT diff --git a/src/serialport/ttylocker_unix.cpp b/src/serialport/ttylocker_unix.cpp new file mode 100644 index 0000000..3e36d67 --- /dev/null +++ b/src/serialport/ttylocker_unix.cpp @@ -0,0 +1,178 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Denis Shienkov <scapig@yandex.ru> +** Contact: http://www.qt-project.org/ +** +** This file is part of the QtSerialPort module 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 "ttylocker_unix_p.h" + +#ifdef HAVE_BAUDBOY_H +# include <baudboy.h> +# include <cstdlib> +#elif defined (HAVE_LOCKDEV_H) +# include <lockdev.h> +# include <unistd.h> +#else +# include <signal.h> +# include <errno.h> +# include <fcntl.h> +# include <sys/stat.h> +# include <unistd.h> +# include <QtCore/qfile.h> +# include <QtCore/qdir.h> +# include <QtCore/qstringlist.h> +#endif // defined (HAVE_BAUDBOY_H) + +QT_BEGIN_NAMESPACE_SERIALPORT + +#if !(defined (HAVE_BAUDBOY_H) || defined (HAVE_LOCKDEV_H)) + +static +const char * const entryLockDirectoryList[] = { + "/var/lock", + "/etc/locks", + "/var/spool/locks", + "/var/spool/uucp", + "/tmp", + 0 +}; + +// Returns the full path first found in the directory where you can create a lock file +// (ie a directory with access to the read/write). +// Verification of directories is of the order in accordance with the order +// of records in the variable lockDirList. +static +QString lookupFirstSharedLockDir() +{ + for (int i = 0; entryLockDirectoryList[i] != 0; ++i) { + if (::access(entryLockDirectoryList[i], R_OK | W_OK) == 0) + return QLatin1String(entryLockDirectoryList[i]); + } + return QString(); +} + +// Returns the name of the lock file which is tied to the +// device name, eg "LCK..ttyS0", etc. +static +QString generateLockFileNameAsNamedForm(const char *portName) +{ + QString result(lookupFirstSharedLockDir()); + if (!result.isEmpty()) + result.append(QLatin1String("/LCK..") + QLatin1String(portName)); + return result; +} + +#endif //!(defined (HAVE_BAUDBOY_H) || defined (HAVE_LOCKDEV_H)) + +// Try lock serial device. However, other processes can not access it. +bool TtyLocker::lock(const char *portName) +{ +#ifdef HAVE_BAUDBOY_H + if (::ttylock(portName) + ::ttywait(portName); + return ::ttylock(portName) != -1; +#elif defined (HAVE_LOCKDEV_H) + return ::dev_lock(portName) != -1; +#else + QFile f(generateLockFileNameAsNamedForm(portName)); + if (f.open(QIODevice::WriteOnly | QIODevice::Truncate)) { + QString content(QLatin1String(" %1 %2\x0A")); + content = content.arg(::getpid()).arg(::getuid()); + if (f.write(content.toLocal8Bit()) > 0) { + f.close(); + return true; + } + f.close(); + } + return false; +#endif +} + +// Try unlock serial device. However, other processes can access it. +bool TtyLocker::unlock(const char *portName) +{ +#ifdef HAVE_BAUDBOY_H + return ::ttyunlock(portName != -1; +#elif defined (HAVE_LOCKDEV_H) + return ::dev_unlock(portName, ::getpid()) != -1; +#else + QFile f(generateLockFileNameAsNamedForm(portName)); + return f.remove(); +#endif +} + +// Verifies the device is locked or not. +// If returned currentPid = true - this means that the device is locked the current process. +bool TtyLocker::isLocked(const char *portName, bool *currentPid) +{ + if (!currentPid) + return true; + + *currentPid = false; + +#ifdef HAVE_BAUDBOY_H + return ::ttylocked(portName) != -1; +#elif defined (HAVE_LOCKDEV_H) + return ::dev_testlock(portName) != -1; +#else + + QFile f(generateLockFileNameAsNamedForm(portName)); + if (!f.exists()) + return false; + if (!f.open(QIODevice::ReadOnly)) + return true; + + QString content(QLatin1String(f.readAll())); + f.close(); + + const pid_t pid = content.section(' ', 0, 0, QString::SectionSkipEmpty).toInt(); + + if (::kill(pid, 0) == -1) { + if (errno == ESRCH) // Process does not exists + return false; + } else { + if (::getpid() == pid) // Process exists and it is "their", i.e current + *currentPid = true; + } + + return true; + +#endif +} + +QT_END_NAMESPACE_SERIALPORT diff --git a/src/serialport/ttylocker_unix_p.h b/src/serialport/ttylocker_unix_p.h new file mode 100644 index 0000000..ed93fb1 --- /dev/null +++ b/src/serialport/ttylocker_unix_p.h @@ -0,0 +1,59 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Denis Shienkov <scapig@yandex.ru> +** Contact: http://www.qt-project.org/ +** +** This file is part of the QtSerialPort module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this +** file. Please review the following information to ensure the GNU Lesser +** General Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef TTYLOCKER_UNIX_P_H +#define TTYLOCKER_UNIX_P_H + +#include "serialport-global.h" + +QT_BEGIN_NAMESPACE_SERIALPORT + +class TtyLocker +{ +public: + static bool lock(const char *portName); + static bool unlock(const char *portName); + static bool isLocked(const char *portName, bool *currentPid); +}; + +QT_END_NAMESPACE_SERIALPORT + +#endif // TTYLOCKER_UNIX_P_H |