1 /**************************************************************************
3 ** This file is part of Qt Creator
5 ** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
7 ** Contact: Nokia Corporation (qt-info@nokia.com)
11 ** This file contains pre-release code and may not be distributed.
12 ** You may use this file in accordance with the terms and conditions
13 ** contained in the Technology Preview License Agreement accompanying
16 ** GNU Lesser General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU Lesser
19 ** General Public License version 2.1 as published by the Free Software
20 ** Foundation and appearing in the file LICENSE.LGPL included in the
21 ** packaging of this file. Please review the following information to
22 ** ensure the GNU Lesser General Public License version 2.1 requirements
23 ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
25 ** In addition, as a special exception, Nokia gives you certain additional
26 ** rights. These rights are described in the Nokia Qt LGPL Exception
27 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
29 ** If you have questions regarding the use of this file, please contact
30 ** Nokia at qt-info@nokia.com.
32 **************************************************************************/
34 #include "virtualserialdevice.h"
36 #include <QtCore/private/qwineventnotifier_p.h>
37 #include <QtCore/QThread>
38 #include <QtCore/QWaitCondition>
40 namespace SymbianUtils {
42 class VirtualSerialDevicePrivate
46 OVERLAPPED writeOverlapped;
47 OVERLAPPED commEventOverlapped;
49 QWinEventNotifier *writeCompleteNotifier;
50 QWinEventNotifier *commEventNotifier;
53 void VirtualSerialDevice::platInit()
55 d = new VirtualSerialDevicePrivate;
56 d->portHandle = INVALID_HANDLE_VALUE;
57 d->writeCompleteNotifier = NULL;
58 memset(&d->writeOverlapped, 0, sizeof(OVERLAPPED));
59 d->commEventNotifier = NULL;
60 memset(&d->commEventOverlapped, 0, sizeof(OVERLAPPED));
63 QString windowsPortName(const QString& port)
65 // Add the \\.\ to the name if it's a COM port and doesn't already have it
66 QString winPortName(port);
67 if (winPortName.startsWith(QLatin1String("COM"))) {
68 winPortName.prepend("\\\\.\\");
73 // Copied from \creator\src\libs\utils\winutils.cpp
74 QString winErrorMessage(unsigned long error)
76 // Some of the windows error messages are a bit too obscure
79 case ERROR_FILE_NOT_FOUND:
81 return VirtualSerialDevice::tr("Port not found");
83 case ERROR_ACCESS_DENIED:
84 return VirtualSerialDevice::tr("Port in use");
85 case ERROR_SEM_TIMEOUT: // Bluetooth ports sometimes return this
86 return VirtualSerialDevice::tr("Timed out");
87 case ERROR_NETWORK_UNREACHABLE:
88 return VirtualSerialDevice::tr("Port unreachable"); // I don't know what this error indicates... from observation, that the windows Bluetooth stack has got itself into a state and needs resetting
93 QString rc = QString::fromLatin1("#%1: ").arg(error);
96 const int len = FormatMessage(
97 FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
98 NULL, error, 0, (LPTSTR)&lpMsgBuf, 0, NULL);
100 rc = QString::fromUtf16(lpMsgBuf, len);
103 rc += QString::fromLatin1("<unknown error>");
108 bool VirtualSerialDevice::open(OpenMode mode)
110 Q_ASSERT(QThread::currentThread() == thread());
111 if (isOpen()) return true;
113 d->portHandle = CreateFileA(windowsPortName(portName).toAscii(), GENERIC_READ|GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL);
114 if (d->portHandle == INVALID_HANDLE_VALUE) {
115 setErrorString(tr("The port %1 could not be opened: %2").
116 arg(portName, winErrorMessage(GetLastError())));
121 memset(&commState, 0, sizeof(DCB));
122 commState.DCBlength = sizeof(DCB);
123 bool ok = GetCommState(d->portHandle, &commState);
125 commState.BaudRate = CBR_115200;
126 commState.fBinary = TRUE;
127 commState.fParity = FALSE;
128 commState.fOutxCtsFlow = FALSE;
129 commState.fOutxDsrFlow = FALSE;
130 commState.fInX = FALSE;
131 commState.fOutX = FALSE;
132 commState.fNull = FALSE;
133 commState.fAbortOnError = FALSE;
134 commState.fDsrSensitivity = FALSE;
135 commState.fDtrControl = DTR_CONTROL_DISABLE;
136 commState.ByteSize = 8;
137 commState.Parity = NOPARITY;
138 commState.StopBits = ONESTOPBIT;
139 ok = SetCommState(d->portHandle, &commState);
142 qWarning("%s setting comm state", qPrintable(winErrorMessage(GetLastError())));
145 // http://msdn.microsoft.com/en-us/library/aa363190(v=vs.85).aspx says this means
146 // "the read operation is to return immediately with the bytes that have already been received, even if no bytes have been received"
147 COMMTIMEOUTS timeouts;
148 timeouts.ReadIntervalTimeout = MAXDWORD;
149 timeouts.ReadTotalTimeoutMultiplier = 0;
150 timeouts.ReadTotalTimeoutConstant = 0;
151 timeouts.WriteTotalTimeoutMultiplier = 0;
152 timeouts.WriteTotalTimeoutConstant = 0;
153 SetCommTimeouts(d->portHandle, &timeouts);
155 d->writeOverlapped.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
156 d->writeCompleteNotifier = new QWinEventNotifier(d->writeOverlapped.hEvent, this);
157 connect(d->writeCompleteNotifier, SIGNAL(activated(HANDLE)), this, SLOT(writeCompleted()));
159 // This is how we implement readyRead notifications
160 d->commEventOverlapped.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
161 d->commEventNotifier = new QWinEventNotifier(d->commEventOverlapped.hEvent, this);
162 connect(d->commEventNotifier, SIGNAL(activated(HANDLE)), this, SLOT(commEventOccurred()));
164 if (!SetCommMask(d->portHandle, EV_RXCHAR)) {
166 qWarning("%s: Could not set comm mask, err=%d", Q_FUNC_INFO, (int)GetLastError());
168 bool result = WaitCommEvent(d->portHandle, &d->commEventMask, &d->commEventOverlapped);
169 Q_ASSERT(result == false); // Can't see how it would make sense to be anything else...
170 (void)result; // For release build
171 if (GetLastError() != ERROR_IO_PENDING) {
172 setErrorString(tr("An error occurred while waiting for read notifications from %1: %2").
173 arg(portName, winErrorMessage(GetLastError())));
178 ok = QIODevice::open(mode);
183 void VirtualSerialDevice::platClose()
185 delete d->writeCompleteNotifier;
186 d->writeCompleteNotifier = NULL;
187 CloseHandle(d->writeOverlapped.hEvent);
188 d->writeOverlapped.hEvent = INVALID_HANDLE_VALUE;
190 delete d->commEventNotifier;
191 d->commEventNotifier = NULL;
192 d->commEventOverlapped.hEvent = INVALID_HANDLE_VALUE;
194 CloseHandle(d->portHandle);
195 d->portHandle = INVALID_HANDLE_VALUE;
198 VirtualSerialDevice::~VirtualSerialDevice()
204 qint64 VirtualSerialDevice::bytesAvailable() const
206 QMutexLocker locker(&lock);
207 if (!isOpen()) return 0;
211 if (ClearCommError(d->portHandle, NULL, &Status)) {
212 avail = Status.cbInQue;
214 return avail + QIODevice::bytesAvailable();
217 void VirtualSerialDevice::commEventOccurred()
219 DWORD event = d->commEventMask;
220 if (event & EV_RXCHAR) {
223 ResetEvent(d->commEventOverlapped.hEvent);
224 WaitCommEvent(d->portHandle, &d->commEventMask, &d->commEventOverlapped);
227 qint64 VirtualSerialDevice::readData(char *data, qint64 maxSize)
229 QMutexLocker locker(&lock);
230 // We do our reads synchronously
231 OVERLAPPED readOverlapped;
232 memset(&readOverlapped, 0, sizeof(OVERLAPPED));
234 BOOL done = ReadFile(d->portHandle, data, maxSize, &bytesRead, &readOverlapped);
235 if (done) return (qint64)bytesRead;
237 if (GetLastError() == ERROR_IO_PENDING) {
238 // Note the TRUE to wait for the read to complete
239 done = GetOverlappedResult(d->portHandle, &readOverlapped, &bytesRead, TRUE);
240 if (done) return (qint64)bytesRead;
243 // If we reach here an error has occurred
244 setErrorString(tr("An error occurred while reading from %1: %2").
245 arg(portName, winErrorMessage(GetLastError())));
250 qint64 VirtualSerialDevice::writeData(const char *data, qint64 maxSize)
252 QMutexLocker locker(&lock);
254 pendingWrites.append(QByteArray(data, maxSize)); // Can't see a way of doing async io safely without having to copy here...
255 if (pendingWrites.count() == 1) {
256 return writeNextBuffer(locker);
262 qint64 VirtualSerialDevice::writeNextBuffer(QMutexLocker& locker)
265 // Must be locked on entry
266 qint64 bufLen = pendingWrites[0].length();
267 BOOL ok = WriteFile(d->portHandle, pendingWrites[0].constData(), bufLen, NULL, &d->writeOverlapped);
268 if (ok || GetLastError() == ERROR_IO_PENDING) {
269 // Apparently it can return true for a small asynchronous write...
270 // Hopefully it still gets signalled in the same way!
272 // Wait for signal via writeCompleted
276 setErrorString(tr("An error occurred while writing to %1: %2").
277 arg(portName, winErrorMessage(GetLastError())));
278 pendingWrites.removeFirst();
283 void VirtualSerialDevice::writeCompleted()
285 QMutexLocker locker(&lock);
286 if (pendingWrites.count() == 0) {
287 qWarning("%s: writeCompleted called when there are no pending writes on %s!",
288 Q_FUNC_INFO, qPrintable(portName));
292 doWriteCompleted(locker);
295 void VirtualSerialDevice::doWriteCompleted(QMutexLocker &locker)
297 // Must be locked on entry
298 ResetEvent(d->writeOverlapped.hEvent);
300 qint64 len = pendingWrites.first().length();
301 pendingWrites.removeFirst();
303 if (pendingWrites.count() > 0) {
304 // Get the next write started before notifying in case client calls waitForBytesWritten in their slot
305 writeNextBuffer(locker);
308 emitBytesWrittenIfNeeded(locker, len);
311 bool VirtualSerialDevice::waitForBytesWritten(int msecs)
313 QMutexLocker locker(&lock);
314 if (pendingWrites.count() == 0) return false;
316 if (QThread::currentThread() != thread()) {
317 // Wait for signal from main thread
318 unsigned long timeout = msecs;
319 if (msecs == -1) timeout = ULONG_MAX;
320 if (waiterForBytesWritten == NULL)
321 waiterForBytesWritten = new QWaitCondition;
322 return waiterForBytesWritten->wait(&lock, timeout);
325 DWORD waitTime = msecs;
326 if (msecs == -1) waitTime = INFINITE; // Ok these are probably bitwise the same, but just to prove I've thought about it...
327 DWORD result = WaitForSingleObject(d->writeOverlapped.hEvent, waitTime); // Do I need WaitForSingleObjectEx and worry about alertable states?
328 if (result == WAIT_TIMEOUT) {
331 else if (result == WAIT_OBJECT_0) {
333 BOOL ok = GetOverlappedResult(d->portHandle, &d->writeOverlapped, &bytesWritten, TRUE);
335 setErrorString(tr("An error occurred while syncing on waitForBytesWritten for %1: %2").
336 arg(portName, winErrorMessage(GetLastError())));
339 Q_ASSERT(bytesWritten == (DWORD)pendingWrites.first().length());
341 doWriteCompleted(locker);
345 setErrorString(QString("An error occured in waitForBytesWritten() for %1: %2").
346 arg(portName, winErrorMessage(GetLastError())));
351 void VirtualSerialDevice::flush()
353 while (waitForBytesWritten(-1)) { /* loop */ }
356 bool VirtualSerialDevice::waitForReadyRead(int msecs)
358 return QIODevice::waitForReadyRead(msecs); //TODO
361 } // namespace SymbianUtils