You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
510 lines
14 KiB
510 lines
14 KiB
12 years ago
|
/****************************************************************************
|
||
|
**
|
||
|
** Copyright (C) 2012 Denis Shienkov <denis.shienkov@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/legal
|
||
|
**
|
||
|
** This file is part of the QtSerialPort module of the Qt Toolkit.
|
||
|
**
|
||
|
** $QT_BEGIN_LICENSE:LGPL$
|
||
|
** Commercial License Usage
|
||
|
** Licensees holding valid commercial Qt licenses may use this file in
|
||
|
** accordance with the commercial license agreement provided with the
|
||
|
** Software or, alternatively, in accordance with the terms contained in
|
||
|
** a written agreement between you and Digia. For licensing terms and
|
||
|
** conditions see http://qt.digia.com/licensing. For further information
|
||
|
** use the contact form at http://qt.digia.com/contact-us.
|
||
|
**
|
||
|
** GNU Lesser General Public License Usage
|
||
|
** Alternatively, this file may be used under the terms of the GNU Lesser
|
||
|
** General Public License version 2.1 as published by the Free Software
|
||
|
** Foundation and appearing in the file LICENSE.LGPL included in the
|
||
|
** packaging of this file. Please review the following information to
|
||
|
** ensure the GNU Lesser General Public License version 2.1 requirements
|
||
|
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
|
||
|
**
|
||
|
** In addition, as a special exception, Digia gives you certain additional
|
||
|
** rights. These rights are described in the Digia 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.
|
||
|
**
|
||
|
**
|
||
|
** $QT_END_LICENSE$
|
||
|
**
|
||
|
****************************************************************************/
|
||
|
|
||
|
#include "qserialport_win_p.h"
|
||
|
|
||
|
#include <QtCore/qelapsedtimer.h>
|
||
|
|
||
|
#include <QtCore/qthread.h>
|
||
|
#include <QtCore/qtimer.h>
|
||
|
|
||
|
QT_BEGIN_NAMESPACE
|
||
|
|
||
|
class QSerialPortPrivate;
|
||
|
|
||
|
class CommEventNotifier : public QThread
|
||
|
{
|
||
|
Q_OBJECT
|
||
|
signals:
|
||
|
void eventMask(quint32 mask);
|
||
|
|
||
|
public:
|
||
|
CommEventNotifier(DWORD mask, QSerialPortPrivate *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:
|
||
|
void run() Q_DECL_OVERRIDE {
|
||
|
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) {
|
||
|
|
||
|
bool error = false;
|
||
|
|
||
|
// Check for unexpected event. This event triggered when pulled previously
|
||
|
// opened device from the system, when opened as for not to read and not to
|
||
|
// write options and so forth.
|
||
|
if ((eventMask == 0)
|
||
|
|| ((eventMask & (EV_ERR | EV_RXCHAR | EV_TXEMPTY)) == 0)) {
|
||
|
error = true;
|
||
|
}
|
||
|
|
||
|
if (error || (EV_ERR & eventMask))
|
||
|
dptr->processIoErrors(error);
|
||
|
if (EV_RXCHAR & eventMask)
|
||
|
dptr->notifyRead();
|
||
|
if (EV_TXEMPTY & eventMask)
|
||
|
dptr->notifyWrite(QSerialPortPrivateData::WriteChunkSize);
|
||
|
}
|
||
|
|
||
|
private:
|
||
|
QSerialPortPrivate *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;
|
||
|
};
|
||
|
|
||
|
QSerialPortPrivate::QSerialPortPrivate(QSerialPort *q)
|
||
|
: QSerialPortPrivateData(q)
|
||
|
, descriptor(INVALID_HANDLE_VALUE)
|
||
|
, parityErrorOccurred(false)
|
||
|
, eventNotifier(0)
|
||
|
{
|
||
|
}
|
||
|
|
||
|
bool QSerialPortPrivate::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) {
|
||
|
q_ptr->setError(decodeSystemError());
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if (!::GetCommState(descriptor, &restoredDcb)) {
|
||
|
q_ptr->setError(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)) {
|
||
|
q_ptr->setError(decodeSystemError());
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
::memset(¤tCommTimeouts, 0, sizeof(currentCommTimeouts));
|
||
|
currentCommTimeouts.ReadIntervalTimeout = MAXDWORD;
|
||
|
|
||
|
if (!updateCommTimeouts())
|
||
|
return false;
|
||
|
|
||
|
eventNotifier = new CommEventNotifier(eventMask, this, q_ptr);
|
||
|
eventNotifier->start();
|
||
|
|
||
|
detectDefaultSettings();
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
void QSerialPortPrivate::close()
|
||
|
{
|
||
|
if (eventNotifier) {
|
||
|
eventNotifier->deleteLater();
|
||
|
eventNotifier = 0;
|
||
|
}
|
||
|
|
||
|
if (settingsRestoredOnClose) {
|
||
|
::SetCommState(descriptor, &restoredDcb);
|
||
|
::SetCommTimeouts(descriptor, &restoredCommTimeouts);
|
||
|
}
|
||
|
|
||
|
::CloseHandle(descriptor);
|
||
|
descriptor = INVALID_HANDLE_VALUE;
|
||
|
}
|
||
|
|
||
|
bool QSerialPortPrivate::flush()
|
||
|
{
|
||
|
return notifyWrite() && ::FlushFileBuffers(descriptor);
|
||
|
}
|
||
|
|
||
|
bool QSerialPortPrivate::clear(QSerialPort::Directions dir)
|
||
|
{
|
||
|
DWORD flags = 0;
|
||
|
if (dir & QSerialPort::Input)
|
||
|
flags |= PURGE_RXABORT | PURGE_RXCLEAR;
|
||
|
if (dir & QSerialPort::Output)
|
||
|
flags |= PURGE_TXABORT | PURGE_TXCLEAR;
|
||
|
return ::PurgeComm(descriptor, flags);
|
||
|
}
|
||
|
|
||
|
qint64 QSerialPortPrivate::bytesAvailable() const
|
||
|
{
|
||
|
return readBuffer.size();
|
||
|
}
|
||
|
|
||
|
qint64 QSerialPortPrivate::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 QSerialPortPrivate::writeToBuffer(const char *data, qint64 maxSize)
|
||
|
{
|
||
|
char *ptr = writeBuffer.reserve(maxSize);
|
||
|
if (maxSize == 1)
|
||
|
*ptr = *data;
|
||
|
else
|
||
|
::memcpy(ptr, data, maxSize);
|
||
|
|
||
|
// trigger write sequence
|
||
|
notifyWrite(QSerialPortPrivateData::WriteChunkSize);
|
||
|
|
||
|
return maxSize;
|
||
|
}
|
||
|
|
||
|
bool QSerialPortPrivate::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 QSerialPortPrivate::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 QSerialPortPrivate::notifyRead()
|
||
|
{
|
||
|
DWORD bytesToRead = (policy == QSerialPort::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);
|
||
|
q_ptr->setError(QSerialPort::ReadError);
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
readBuffer.truncate(readBytes);
|
||
|
|
||
|
// Process emulate policy.
|
||
|
if ((policy != QSerialPort::IgnorePolicy) && parityErrorOccurred) {
|
||
|
|
||
|
parityErrorOccurred = false;
|
||
|
|
||
|
switch (policy) {
|
||
|
case QSerialPort::SkipPolicy:
|
||
|
readBuffer.getChar();
|
||
|
return true;
|
||
|
case QSerialPort::PassZeroPolicy:
|
||
|
readBuffer.getChar();
|
||
|
readBuffer.putChar('\0');
|
||
|
break;
|
||
|
case QSerialPort::StopReceivingPolicy:
|
||
|
// FIXME: Maybe need disable read notifier?
|
||
|
break;
|
||
|
default:
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (readBytes > 0)
|
||
|
emit q_ptr->readyRead();
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
bool QSerialPortPrivate::notifyWrite(int maxSize)
|
||
|
{
|
||
|
int nextSize = qMin(writeBuffer.nextDataBlockSize(), maxSize);
|
||
|
|
||
|
const char *ptr = writeBuffer.readPointer();
|
||
|
|
||
|
DWORD bytesWritten = 0;
|
||
|
if (!::WriteFile(descriptor, ptr, nextSize, &bytesWritten, NULL)) {
|
||
|
q_ptr->setError(QSerialPort::WriteError);
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
writeBuffer.free(bytesWritten);
|
||
|
|
||
|
if (bytesWritten > 0)
|
||
|
emit q_ptr->bytesWritten(bytesWritten);
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
bool QSerialPortPrivate::waitForReadOrWrite(bool *selectForRead, bool *selectForWrite,
|
||
|
bool checkRead, bool checkWrite,
|
||
|
int msecs, bool *timedOut)
|
||
|
{
|
||
|
DWORD eventMask = 0;
|
||
|
// 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 QSerialPortPrivate::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)
|
||
|
q_ptr->setError(decodeSystemError());
|
||
|
// Restore the event mask
|
||
|
::SetCommMask(descriptor, eventMask);
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
bool QSerialPortPrivate::updateCommTimeouts()
|
||
|
{
|
||
|
if (!::SetCommTimeouts(descriptor, ¤tCommTimeouts)) {
|
||
|
q_ptr->setError(decodeSystemError());
|
||
|
return false;
|
||
|
}
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
static const QLatin1String defaultPathPostfix(":");
|
||
|
|
||
|
QString QSerialPortPrivate::portNameToSystemLocation(const QString &port)
|
||
|
{
|
||
|
QString ret = port;
|
||
|
if (!ret.contains(defaultPathPostfix))
|
||
|
ret.append(defaultPathPostfix);
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
QString QSerialPortPrivate::portNameFromSystemLocation(const QString &location)
|
||
|
{
|
||
|
QString ret = location;
|
||
|
if (ret.contains(defaultPathPostfix))
|
||
|
ret.remove(defaultPathPostfix);
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
#include "qserialport_wince.moc"
|
||
|
|
||
|
QT_END_NAMESPACE
|