diff --git a/Ico/icon.png b/Ico/icon.png new file mode 100644 index 0000000..f0fd832 Binary files /dev/null and b/Ico/icon.png differ diff --git a/Sources/qextserialport/posix_qextserialport.cpp b/Sources/qextserialport/posix_qextserialport.cpp new file mode 100644 index 0000000..c7f923c --- /dev/null +++ b/Sources/qextserialport/posix_qextserialport.cpp @@ -0,0 +1,959 @@ + + +#include +#include +#include "qextserialport.h" +#include +#include + +void QextSerialPort::platformSpecificInit() +{ + fd = 0; + readNotifier = 0; +} + +/*! +Standard destructor. +*/ +void QextSerialPort::platformSpecificDestruct() +{} + +/*! +Sets the baud rate of the serial port. Note that not all rates are applicable on +all platforms. The following table shows translations of the various baud rate +constants on Windows(including NT/2000) and POSIX platforms. Speeds marked with an * +are speeds that are usable on both Windows and POSIX. + +\note +BAUD76800 may not be supported on all POSIX systems. SGI/IRIX systems do not support +BAUD1800. + +\verbatim + + RATE Windows Speed POSIX Speed + ----------- ------------- ----------- + BAUD50 110 50 + BAUD75 110 75 + *BAUD110 110 110 + BAUD134 110 134.5 + BAUD150 110 150 + BAUD200 110 200 + *BAUD300 300 300 + *BAUD600 600 600 + *BAUD1200 1200 1200 + BAUD1800 1200 1800 + *BAUD2400 2400 2400 + *BAUD4800 4800 4800 + *BAUD9600 9600 9600 + BAUD14400 14400 9600 + *BAUD19200 19200 19200 + *BAUD38400 38400 38400 + BAUD56000 56000 38400 + *BAUD57600 57600 57600 + BAUD76800 57600 76800 + *BAUD115200 115200 115200 + BAUD128000 128000 115200 + BAUD256000 256000 115200 +\endverbatim +*/ +void QextSerialPort::setBaudRate(BaudRateType baudRate) +{ + QMutexLocker lock(mutex); + if (Settings.BaudRate!=baudRate) { + switch (baudRate) { + case BAUD14400: + Settings.BaudRate=BAUD9600; + break; + + case BAUD56000: + Settings.BaudRate=BAUD38400; + break; + + case BAUD76800: + +#ifndef B76800 + Settings.BaudRate=BAUD57600; +#else + Settings.BaudRate=baudRate; +#endif + break; + + case BAUD128000: + case BAUD256000: + Settings.BaudRate=BAUD115200; + break; + + default: + Settings.BaudRate=baudRate; + break; + } + } + if (isOpen()) { + switch (baudRate) { + + /*50 baud*/ + case BAUD50: + TTY_PORTABILITY_WARNING("QextSerialPort Portability Warning: Windows does not support 50 baud operation."); +#ifdef CBAUD + Posix_CommConfig.c_cflag&=(~CBAUD); + Posix_CommConfig.c_cflag|=B50; +#else + cfsetispeed(&Posix_CommConfig, B50); + cfsetospeed(&Posix_CommConfig, B50); +#endif + break; + + /*75 baud*/ + case BAUD75: + TTY_PORTABILITY_WARNING("QextSerialPort Portability Warning: Windows does not support 75 baud operation."); +#ifdef CBAUD + Posix_CommConfig.c_cflag&=(~CBAUD); + Posix_CommConfig.c_cflag|=B75; +#else + cfsetispeed(&Posix_CommConfig, B75); + cfsetospeed(&Posix_CommConfig, B75); +#endif + break; + + /*110 baud*/ + case BAUD110: +#ifdef CBAUD + Posix_CommConfig.c_cflag&=(~CBAUD); + Posix_CommConfig.c_cflag|=B110; +#else + cfsetispeed(&Posix_CommConfig, B110); + cfsetospeed(&Posix_CommConfig, B110); +#endif + break; + + /*134.5 baud*/ + case BAUD134: + TTY_PORTABILITY_WARNING("QextSerialPort Portability Warning: Windows does not support 134.5 baud operation."); +#ifdef CBAUD + Posix_CommConfig.c_cflag&=(~CBAUD); + Posix_CommConfig.c_cflag|=B134; +#else + cfsetispeed(&Posix_CommConfig, B134); + cfsetospeed(&Posix_CommConfig, B134); +#endif + break; + + /*150 baud*/ + case BAUD150: + TTY_PORTABILITY_WARNING("QextSerialPort Portability Warning: Windows does not support 150 baud operation."); +#ifdef CBAUD + Posix_CommConfig.c_cflag&=(~CBAUD); + Posix_CommConfig.c_cflag|=B150; +#else + cfsetispeed(&Posix_CommConfig, B150); + cfsetospeed(&Posix_CommConfig, B150); +#endif + break; + + /*200 baud*/ + case BAUD200: + TTY_PORTABILITY_WARNING("QextSerialPort Portability Warning: Windows does not support 200 baud operation."); +#ifdef CBAUD + Posix_CommConfig.c_cflag&=(~CBAUD); + Posix_CommConfig.c_cflag|=B200; +#else + cfsetispeed(&Posix_CommConfig, B200); + cfsetospeed(&Posix_CommConfig, B200); +#endif + break; + + /*300 baud*/ + case BAUD300: +#ifdef CBAUD + Posix_CommConfig.c_cflag&=(~CBAUD); + Posix_CommConfig.c_cflag|=B300; +#else + cfsetispeed(&Posix_CommConfig, B300); + cfsetospeed(&Posix_CommConfig, B300); +#endif + break; + + /*600 baud*/ + case BAUD600: +#ifdef CBAUD + Posix_CommConfig.c_cflag&=(~CBAUD); + Posix_CommConfig.c_cflag|=B600; +#else + cfsetispeed(&Posix_CommConfig, B600); + cfsetospeed(&Posix_CommConfig, B600); +#endif + break; + + /*1200 baud*/ + case BAUD1200: +#ifdef CBAUD + Posix_CommConfig.c_cflag&=(~CBAUD); + Posix_CommConfig.c_cflag|=B1200; +#else + cfsetispeed(&Posix_CommConfig, B1200); + cfsetospeed(&Posix_CommConfig, B1200); +#endif + break; + + /*1800 baud*/ + case BAUD1800: + TTY_PORTABILITY_WARNING("QextSerialPort Portability Warning: Windows and IRIX do not support 1800 baud operation."); +#ifdef CBAUD + Posix_CommConfig.c_cflag&=(~CBAUD); + Posix_CommConfig.c_cflag|=B1800; +#else + cfsetispeed(&Posix_CommConfig, B1800); + cfsetospeed(&Posix_CommConfig, B1800); +#endif + break; + + /*2400 baud*/ + case BAUD2400: +#ifdef CBAUD + Posix_CommConfig.c_cflag&=(~CBAUD); + Posix_CommConfig.c_cflag|=B2400; +#else + cfsetispeed(&Posix_CommConfig, B2400); + cfsetospeed(&Posix_CommConfig, B2400); +#endif + break; + + /*4800 baud*/ + case BAUD4800: +#ifdef CBAUD + Posix_CommConfig.c_cflag&=(~CBAUD); + Posix_CommConfig.c_cflag|=B4800; +#else + cfsetispeed(&Posix_CommConfig, B4800); + cfsetospeed(&Posix_CommConfig, B4800); +#endif + break; + + /*9600 baud*/ + case BAUD9600: +#ifdef CBAUD + Posix_CommConfig.c_cflag&=(~CBAUD); + Posix_CommConfig.c_cflag|=B9600; +#else + cfsetispeed(&Posix_CommConfig, B9600); + cfsetospeed(&Posix_CommConfig, B9600); +#endif + break; + + /*14400 baud*/ + case BAUD14400: + TTY_WARNING("QextSerialPort: POSIX does not support 14400 baud operation. Switching to 9600 baud."); +#ifdef CBAUD + Posix_CommConfig.c_cflag&=(~CBAUD); + Posix_CommConfig.c_cflag|=B9600; +#else + cfsetispeed(&Posix_CommConfig, B9600); + cfsetospeed(&Posix_CommConfig, B9600); +#endif + break; + + /*19200 baud*/ + case BAUD19200: +#ifdef CBAUD + Posix_CommConfig.c_cflag&=(~CBAUD); + Posix_CommConfig.c_cflag|=B19200; +#else + cfsetispeed(&Posix_CommConfig, B19200); + cfsetospeed(&Posix_CommConfig, B19200); +#endif + break; + + /*38400 baud*/ + case BAUD38400: +#ifdef CBAUD + Posix_CommConfig.c_cflag&=(~CBAUD); + Posix_CommConfig.c_cflag|=B38400; +#else + cfsetispeed(&Posix_CommConfig, B38400); + cfsetospeed(&Posix_CommConfig, B38400); +#endif + break; + + /*56000 baud*/ + case BAUD56000: + TTY_WARNING("QextSerialPort: POSIX does not support 56000 baud operation. Switching to 38400 baud."); +#ifdef CBAUD + Posix_CommConfig.c_cflag&=(~CBAUD); + Posix_CommConfig.c_cflag|=B38400; +#else + cfsetispeed(&Posix_CommConfig, B38400); + cfsetospeed(&Posix_CommConfig, B38400); +#endif + break; + + /*57600 baud*/ + case BAUD57600: +#ifdef CBAUD + Posix_CommConfig.c_cflag&=(~CBAUD); + Posix_CommConfig.c_cflag|=B57600; +#else + cfsetispeed(&Posix_CommConfig, B57600); + cfsetospeed(&Posix_CommConfig, B57600); +#endif + break; + + /*76800 baud*/ + case BAUD76800: + TTY_PORTABILITY_WARNING("QextSerialPort Portability Warning: Windows and some POSIX systems do not support 76800 baud operation."); +#ifdef CBAUD + Posix_CommConfig.c_cflag&=(~CBAUD); + +#ifdef B76800 + Posix_CommConfig.c_cflag|=B76800; +#else + TTY_WARNING("QextSerialPort: QextSerialPort was compiled without 76800 baud support. Switching to 57600 baud."); + Posix_CommConfig.c_cflag|=B57600; +#endif //B76800 +#else //CBAUD +#ifdef B76800 + cfsetispeed(&Posix_CommConfig, B76800); + cfsetospeed(&Posix_CommConfig, B76800); +#else + TTY_WARNING("QextSerialPort: QextSerialPort was compiled without 76800 baud support. Switching to 57600 baud."); + cfsetispeed(&Posix_CommConfig, B57600); + cfsetospeed(&Posix_CommConfig, B57600); +#endif //B76800 +#endif //CBAUD + break; + + /*115200 baud*/ + case BAUD115200: +#ifdef CBAUD + Posix_CommConfig.c_cflag&=(~CBAUD); + Posix_CommConfig.c_cflag|=B115200; +#else + cfsetispeed(&Posix_CommConfig, B115200); + cfsetospeed(&Posix_CommConfig, B115200); +#endif + break; + + /*128000 baud*/ + case BAUD128000: + TTY_WARNING("QextSerialPort: POSIX does not support 128000 baud operation. Switching to 115200 baud."); +#ifdef CBAUD + Posix_CommConfig.c_cflag&=(~CBAUD); + Posix_CommConfig.c_cflag|=B115200; +#else + cfsetispeed(&Posix_CommConfig, B115200); + cfsetospeed(&Posix_CommConfig, B115200); +#endif + break; + + /*256000 baud*/ + case BAUD256000: + TTY_WARNING("QextSerialPort: POSIX does not support 256000 baud operation. Switching to 115200 baud."); +#ifdef CBAUD + Posix_CommConfig.c_cflag&=(~CBAUD); + Posix_CommConfig.c_cflag|=B115200; +#else + cfsetispeed(&Posix_CommConfig, B115200); + cfsetospeed(&Posix_CommConfig, B115200); +#endif + break; + } + tcsetattr(fd, TCSAFLUSH, &Posix_CommConfig); + } +} + +/*! +Sets the number of data bits used by the serial port. Possible values of dataBits are: +\verbatim + DATA_5 5 data bits + DATA_6 6 data bits + DATA_7 7 data bits + DATA_8 8 data bits +\endverbatim + +\note +This function is subject to the following restrictions: +\par + 5 data bits cannot be used with 2 stop bits. +\par + 8 data bits cannot be used with space parity on POSIX systems. +*/ +void QextSerialPort::setDataBits(DataBitsType dataBits) +{ + QMutexLocker lock(mutex); + if (Settings.DataBits!=dataBits) { + if ((Settings.StopBits==STOP_2 && dataBits==DATA_5) || + (Settings.StopBits==STOP_1_5 && dataBits!=DATA_5) || + (Settings.Parity==PAR_SPACE && dataBits==DATA_8)) { + } + else { + Settings.DataBits=dataBits; + } + } + if (isOpen()) { + switch(dataBits) { + + /*5 data bits*/ + case DATA_5: + if (Settings.StopBits==STOP_2) { + TTY_WARNING("QextSerialPort: 5 Data bits cannot be used with 2 stop bits."); + } + else { + Settings.DataBits=dataBits; + Posix_CommConfig.c_cflag&=(~CSIZE); + Posix_CommConfig.c_cflag|=CS5; + tcsetattr(fd, TCSAFLUSH, &Posix_CommConfig); + } + break; + + /*6 data bits*/ + case DATA_6: + if (Settings.StopBits==STOP_1_5) { + TTY_WARNING("QextSerialPort: 6 Data bits cannot be used with 1.5 stop bits."); + } + else { + Settings.DataBits=dataBits; + Posix_CommConfig.c_cflag&=(~CSIZE); + Posix_CommConfig.c_cflag|=CS6; + tcsetattr(fd, TCSAFLUSH, &Posix_CommConfig); + } + break; + + /*7 data bits*/ + case DATA_7: + if (Settings.StopBits==STOP_1_5) { + TTY_WARNING("QextSerialPort: 7 Data bits cannot be used with 1.5 stop bits."); + } + else { + Settings.DataBits=dataBits; + Posix_CommConfig.c_cflag&=(~CSIZE); + Posix_CommConfig.c_cflag|=CS7; + tcsetattr(fd, TCSAFLUSH, &Posix_CommConfig); + } + break; + + /*8 data bits*/ + case DATA_8: + if (Settings.StopBits==STOP_1_5) { + TTY_WARNING("QextSerialPort: 8 Data bits cannot be used with 1.5 stop bits."); + } + else { + Settings.DataBits=dataBits; + Posix_CommConfig.c_cflag&=(~CSIZE); + Posix_CommConfig.c_cflag|=CS8; + tcsetattr(fd, TCSAFLUSH, &Posix_CommConfig); + } + break; + } + } +} + +/*! +Sets the parity associated with the serial port. The possible values of parity are: +\verbatim + PAR_SPACE Space Parity + PAR_MARK Mark Parity + PAR_NONE No Parity + PAR_EVEN Even Parity + PAR_ODD Odd Parity +\endverbatim + +\note +This function is subject to the following limitations: +\par +POSIX systems do not support mark parity. +\par +POSIX systems support space parity only if tricked into doing so, and only with + fewer than 8 data bits. Use space parity very carefully with POSIX systems. +*/ +void QextSerialPort::setParity(ParityType parity) +{ + QMutexLocker lock(mutex); + if (Settings.Parity!=parity) { + if (parity==PAR_MARK || (parity==PAR_SPACE && Settings.DataBits==DATA_8)) { + } + else { + Settings.Parity=parity; + } + } + if (isOpen()) { + switch (parity) { + + /*space parity*/ + case PAR_SPACE: + if (Settings.DataBits==DATA_8) { + TTY_PORTABILITY_WARNING("QextSerialPort: Space parity is only supported in POSIX with 7 or fewer data bits"); + } + else { + + /*space parity not directly supported - add an extra data bit to simulate it*/ + Posix_CommConfig.c_cflag&=~(PARENB|CSIZE); + switch(Settings.DataBits) { + case DATA_5: + Settings.DataBits=DATA_6; + Posix_CommConfig.c_cflag|=CS6; + break; + + case DATA_6: + Settings.DataBits=DATA_7; + Posix_CommConfig.c_cflag|=CS7; + break; + + case DATA_7: + Settings.DataBits=DATA_8; + Posix_CommConfig.c_cflag|=CS8; + break; + + case DATA_8: + break; + } + tcsetattr(fd, TCSAFLUSH, &Posix_CommConfig); + } + break; + + /*mark parity - WINDOWS ONLY*/ + case PAR_MARK: + TTY_WARNING("QextSerialPort: Mark parity is not supported by POSIX."); + break; + + /*no parity*/ + case PAR_NONE: + Posix_CommConfig.c_cflag&=(~PARENB); + tcsetattr(fd, TCSAFLUSH, &Posix_CommConfig); + break; + + /*even parity*/ + case PAR_EVEN: + Posix_CommConfig.c_cflag&=(~PARODD); + Posix_CommConfig.c_cflag|=PARENB; + tcsetattr(fd, TCSAFLUSH, &Posix_CommConfig); + break; + + /*odd parity*/ + case PAR_ODD: + Posix_CommConfig.c_cflag|=(PARENB|PARODD); + tcsetattr(fd, TCSAFLUSH, &Posix_CommConfig); + break; + } + } +} + +/*! +Sets the number of stop bits used by the serial port. Possible values of stopBits are: +\verbatim + STOP_1 1 stop bit + STOP_1_5 1.5 stop bits + STOP_2 2 stop bits +\endverbatim +\note +This function is subject to the following restrictions: +\par + 2 stop bits cannot be used with 5 data bits. +\par + POSIX does not support 1.5 stop bits. + +*/ +void QextSerialPort::setStopBits(StopBitsType stopBits) +{ + QMutexLocker lock(mutex); + if (Settings.StopBits!=stopBits) { + if ((Settings.DataBits==DATA_5 && stopBits==STOP_2) || stopBits==STOP_1_5) {} + else { + Settings.StopBits=stopBits; + } + } + if (isOpen()) { + switch (stopBits) { + + /*one stop bit*/ + case STOP_1: + Settings.StopBits=stopBits; + Posix_CommConfig.c_cflag&=(~CSTOPB); + tcsetattr(fd, TCSAFLUSH, &Posix_CommConfig); + break; + + /*1.5 stop bits*/ + case STOP_1_5: + TTY_WARNING("QextSerialPort: 1.5 stop bit operation is not supported by POSIX."); + break; + + /*two stop bits*/ + case STOP_2: + if (Settings.DataBits==DATA_5) { + TTY_WARNING("QextSerialPort: 2 stop bits cannot be used with 5 data bits"); + } + else { + Settings.StopBits=stopBits; + Posix_CommConfig.c_cflag|=CSTOPB; + tcsetattr(fd, TCSAFLUSH, &Posix_CommConfig); + } + break; + } + } +} + +/*! +Sets the flow control used by the port. Possible values of flow are: +\verbatim + FLOW_OFF No flow control + FLOW_HARDWARE Hardware (RTS/CTS) flow control + FLOW_XONXOFF Software (XON/XOFF) flow control +\endverbatim +\note +FLOW_HARDWARE may not be supported on all versions of UNIX. In cases where it is +unsupported, FLOW_HARDWARE is the same as FLOW_OFF. + +*/ +void QextSerialPort::setFlowControl(FlowType flow) +{ + QMutexLocker lock(mutex); + if (Settings.FlowControl!=flow) { + Settings.FlowControl=flow; + } + if (isOpen()) { + switch(flow) { + + /*no flow control*/ + case FLOW_OFF: + Posix_CommConfig.c_cflag&=(~CRTSCTS); + Posix_CommConfig.c_iflag&=(~(IXON|IXOFF|IXANY)); + tcsetattr(fd, TCSAFLUSH, &Posix_CommConfig); + break; + + /*software (XON/XOFF) flow control*/ + case FLOW_XONXOFF: + Posix_CommConfig.c_cflag&=(~CRTSCTS); + Posix_CommConfig.c_iflag|=(IXON|IXOFF|IXANY); + tcsetattr(fd, TCSAFLUSH, &Posix_CommConfig); + break; + + case FLOW_HARDWARE: + Posix_CommConfig.c_cflag|=CRTSCTS; + Posix_CommConfig.c_iflag&=(~(IXON|IXOFF|IXANY)); + tcsetattr(fd, TCSAFLUSH, &Posix_CommConfig); + break; + } + } +} + +/*! +Sets the read and write timeouts for the port to millisec milliseconds. +Note that this is a per-character timeout, i.e. the port will wait this long for each +individual character, not for the whole read operation. This timeout also applies to the +bytesWaiting() function. + +\note +POSIX does not support millisecond-level control for I/O timeout values. Any +timeout set using this function will be set to the next lowest tenth of a second for +the purposes of detecting read or write timeouts. For example a timeout of 550 milliseconds +will be seen by the class as a timeout of 500 milliseconds for the purposes of reading and +writing the port. However millisecond-level control is allowed by the select() system call, +so for example a 550-millisecond timeout will be seen as 550 milliseconds on POSIX systems for +the purpose of detecting available bytes in the read buffer. + +*/ +void QextSerialPort::setTimeout(long millisec) +{ + QMutexLocker lock(mutex); + Settings.Timeout_Millisec = millisec; + Posix_Copy_Timeout.tv_sec = millisec / 1000; + Posix_Copy_Timeout.tv_usec = millisec % 1000; + if (isOpen()) { + if (millisec == -1) + fcntl(fd, F_SETFL, O_NDELAY); + else + //O_SYNC should enable blocking ::write() + //however this seems not working on Linux 2.6.21 (works on OpenBSD 4.2) + fcntl(fd, F_SETFL, O_SYNC); + tcgetattr(fd, & Posix_CommConfig); + Posix_CommConfig.c_cc[VTIME] = millisec/100; + tcsetattr(fd, TCSAFLUSH, & Posix_CommConfig); + } +} + +/*! +Opens the serial port associated to this class. +This function has no effect if the port associated with the class is already open. +The port is also configured to the current settings, as stored in the Settings structure. +*/ +bool QextSerialPort::open(OpenMode mode) +{ + QMutexLocker lock(mutex); + if (mode == QIODevice::NotOpen) + return isOpen(); + if (!isOpen()) { + qDebug() << "trying to open file" << port.toAscii(); + //note: linux 2.6.21 seems to ignore O_NDELAY flag + if ((fd = ::open(port.toAscii() ,O_RDWR | O_NOCTTY | O_NDELAY)) != -1) { + qDebug("file opened succesfully"); + + setOpenMode(mode); // Flag the port as opened + tcgetattr(fd, &old_termios); // Save the old termios + Posix_CommConfig = old_termios; // Make a working copy + cfmakeraw(&Posix_CommConfig); // Enable raw access + + /*set up other port settings*/ + Posix_CommConfig.c_cflag|=CREAD|CLOCAL; + Posix_CommConfig.c_lflag&=(~(ICANON|ECHO|ECHOE|ECHOK|ECHONL|ISIG)); + Posix_CommConfig.c_iflag&=(~(INPCK|IGNPAR|PARMRK|ISTRIP|ICRNL|IXANY)); + Posix_CommConfig.c_oflag&=(~OPOST); + Posix_CommConfig.c_cc[VMIN]= 0; +#ifdef _POSIX_VDISABLE // Is a disable character available on this system? + // Some systems allow for per-device disable-characters, so get the + // proper value for the configured device + const long vdisable = fpathconf(fd, _PC_VDISABLE); + Posix_CommConfig.c_cc[VINTR] = vdisable; + Posix_CommConfig.c_cc[VQUIT] = vdisable; + Posix_CommConfig.c_cc[VSTART] = vdisable; + Posix_CommConfig.c_cc[VSTOP] = vdisable; + Posix_CommConfig.c_cc[VSUSP] = vdisable; +#endif //_POSIX_VDISABLE + setBaudRate(Settings.BaudRate); + setDataBits(Settings.DataBits); + setParity(Settings.Parity); + setStopBits(Settings.StopBits); + setFlowControl(Settings.FlowControl); + setTimeout(Settings.Timeout_Millisec); + tcsetattr(fd, TCSAFLUSH, &Posix_CommConfig); + + if (queryMode() == QextSerialPort::EventDriven) { + readNotifier = new QSocketNotifier(fd, QSocketNotifier::Read, this); + connect(readNotifier, SIGNAL(activated(int)), this, SIGNAL(readyRead())); + } + } else { + qDebug() << "could not open file:" << strerror(errno); + lastErr = E_FILE_NOT_FOUND; + } + } + return isOpen(); +} + +/*! +Closes a serial port. This function has no effect if the serial port associated with the class +is not currently open. +*/ +void QextSerialPort::close() +{ + QMutexLocker lock(mutex); + if( isOpen() ) + { + // Force a flush and then restore the original termios + flush(); + // Using both TCSAFLUSH and TCSANOW here discards any pending input + tcsetattr(fd, TCSAFLUSH | TCSANOW, &old_termios); // Restore termios + // Be a good QIODevice and call QIODevice::close() before POSIX close() + // so the aboutToClose() signal is emitted at the proper time + QIODevice::close(); // Flag the device as closed + // QIODevice::close() doesn't actually close the port, so do that here + ::close(fd); + if(readNotifier) { + delete readNotifier; + readNotifier = 0; + } + } +} + +/*! +Flushes all pending I/O to the serial port. This function has no effect if the serial port +associated with the class is not currently open. +*/ +void QextSerialPort::flush() +{ + QMutexLocker lock(mutex); + if (isOpen()) + tcflush(fd, TCIOFLUSH); +} + +/*! +This function will return the number of bytes waiting in the receive queue of the serial port. +It is included primarily to provide a complete QIODevice interface, and will not record errors +in the lastErr member (because it is const). This function is also not thread-safe - in +multithreading situations, use QextSerialPort::bytesWaiting() instead. +*/ +qint64 QextSerialPort::size() const +{ + int numBytes; + if (ioctl(fd, FIONREAD, &numBytes)<0) { + numBytes = 0; + } + return (qint64)numBytes; +} + +/*! +Returns the number of bytes waiting in the port's receive queue. This function will return 0 if +the port is not currently open, or -1 on error. +*/ +qint64 QextSerialPort::bytesAvailable() const +{ + QMutexLocker lock(mutex); + if (isOpen()) { + int bytesQueued; + if (ioctl(fd, FIONREAD, &bytesQueued) == -1) { + return (qint64)-1; + } + return bytesQueued + QIODevice::bytesAvailable(); + } + return 0; +} + +/*! +This function is included to implement the full QIODevice interface, and currently has no +purpose within this class. This function is meaningless on an unbuffered device and currently +only prints a warning message to that effect. +*/ +void QextSerialPort::ungetChar(char) +{ + /*meaningless on unbuffered sequential device - return error and print a warning*/ + TTY_WARNING("QextSerialPort: ungetChar() called on an unbuffered sequential device - operation is meaningless"); +} + +/*! +Translates a system-specific error code to a QextSerialPort error code. Used internally. +*/ +void QextSerialPort::translateError(ulong error) +{ + switch (error) { + case EBADF: + case ENOTTY: + lastErr=E_INVALID_FD; + break; + + case EINTR: + lastErr=E_CAUGHT_NON_BLOCKED_SIGNAL; + break; + + case ENOMEM: + lastErr=E_NO_MEMORY; + break; + } +} + +/*! +Sets DTR line to the requested state (high by default). This function will have no effect if +the port associated with the class is not currently open. +*/ +void QextSerialPort::setDtr(bool set) +{ + QMutexLocker lock(mutex); + if (isOpen()) { + int status; + ioctl(fd, TIOCMGET, &status); + if (set) { + status|=TIOCM_DTR; + } + else { + status&=~TIOCM_DTR; + } + ioctl(fd, TIOCMSET, &status); + } +} + +/*! +Sets RTS line to the requested state (high by default). This function will have no effect if +the port associated with the class is not currently open. +*/ +void QextSerialPort::setRts(bool set) +{ + QMutexLocker lock(mutex); + if (isOpen()) { + int status; + ioctl(fd, TIOCMGET, &status); + if (set) { + status|=TIOCM_RTS; + } + else { + status&=~TIOCM_RTS; + } + ioctl(fd, TIOCMSET, &status); + } +} + +/*! +Returns the line status as stored by the port function. This function will retrieve the states +of the following lines: DCD, CTS, DSR, and RI. On POSIX systems, the following additional lines +can be monitored: DTR, RTS, Secondary TXD, and Secondary RXD. The value returned is an unsigned +long with specific bits indicating which lines are high. The following constants should be used +to examine the states of individual lines: + +\verbatim +Mask Line +------ ---- +LS_CTS CTS +LS_DSR DSR +LS_DCD DCD +LS_RI RI +LS_RTS RTS (POSIX only) +LS_DTR DTR (POSIX only) +LS_ST Secondary TXD (POSIX only) +LS_SR Secondary RXD (POSIX only) +\endverbatim + +This function will return 0 if the port associated with the class is not currently open. +*/ +unsigned long QextSerialPort::lineStatus() +{ + unsigned long Status=0, Temp=0; + QMutexLocker lock(mutex); + if (isOpen()) { + ioctl(fd, TIOCMGET, &Temp); + if (Temp&TIOCM_CTS) { + Status|=LS_CTS; + } + if (Temp&TIOCM_DSR) { + Status|=LS_DSR; + } + if (Temp&TIOCM_RI) { + Status|=LS_RI; + } + if (Temp&TIOCM_CD) { + Status|=LS_DCD; + } + if (Temp&TIOCM_DTR) { + Status|=LS_DTR; + } + if (Temp&TIOCM_RTS) { + Status|=LS_RTS; + } + if (Temp&TIOCM_ST) { + Status|=LS_ST; + } + if (Temp&TIOCM_SR) { + Status|=LS_SR; + } + } + return Status; +} + +/*! +Reads a block of data from the serial port. This function will read at most maxSize bytes from +the serial port and place them in the buffer pointed to by data. Return value is the number of +bytes actually read, or -1 on error. + +\warning before calling this function ensure that serial port associated with this class +is currently open (use isOpen() function to check if port is open). +*/ +qint64 QextSerialPort::readData(char * data, qint64 maxSize) +{ + QMutexLocker lock(mutex); + int retVal = ::read(fd, data, maxSize); + if (retVal == -1) + lastErr = E_READ_FAILED; + + return retVal; +} + +/*! +Writes a block of data to the serial port. This function will write maxSize bytes +from the buffer pointed to by data to the serial port. Return value is the number +of bytes actually written, or -1 on error. + +\warning before calling this function ensure that serial port associated with this class +is currently open (use isOpen() function to check if port is open). +*/ +qint64 QextSerialPort::writeData(const char * data, qint64 maxSize) +{ + QMutexLocker lock(mutex); + int retVal = ::write(fd, data, maxSize); + if (retVal == -1) + lastErr = E_WRITE_FAILED; + + return (qint64)retVal; +} diff --git a/Sources/qextserialport/qextserialenumerator.h b/Sources/qextserialport/qextserialenumerator.h new file mode 100644 index 0000000..329648e --- /dev/null +++ b/Sources/qextserialport/qextserialenumerator.h @@ -0,0 +1,199 @@ +/*! + * \file qextserialenumerator.h + * \author Michal Policht + * \see QextSerialEnumerator + */ + +#ifndef _QEXTSERIALENUMERATOR_H_ +#define _QEXTSERIALENUMERATOR_H_ + + +#include +#include +#include +#include "qextserialport_global.h" + +#ifdef Q_OS_WIN + #include + #include + #include +#endif /*Q_OS_WIN*/ + +#ifdef Q_OS_MAC + #include +#endif + +/*! + * Structure containing port information. + */ +struct QextPortInfo { + QString portName; ///< Port name. + QString physName; ///< Physical name. + QString friendName; ///< Friendly name. + QString enumName; ///< Enumerator name. + int vendorID; ///< Vendor ID. + int productID; ///< Product ID +}; + +#ifdef Q_OS_WIN +#ifdef QT_GUI_LIB +#include +class QextSerialEnumerator; + +class QextSerialRegistrationWidget : public QWidget +{ + Q_OBJECT + public: + QextSerialRegistrationWidget( QextSerialEnumerator* qese ) { + this->qese = qese; + } + ~QextSerialRegistrationWidget( ) { } + + protected: + QextSerialEnumerator* qese; + bool winEvent( MSG* message, long* result ); +}; +#endif // QT_GUI_LIB +#endif // Q_OS_WIN + +/*! + Provides list of ports available in the system. + + \section Usage + To poll the system for a list of connected devices, simply use getPorts(). Each + QextPortInfo structure will populated with information about the corresponding device. + + \b Example + \code + QList ports = QextSerialEnumerator::getPorts(); + foreach( QextPortInfo port, ports ) { + // inspect port... + } + \endcode + + To enable event-driven notification of device connection events, first call + setUpNotifications() and then connect to the deviceDiscovered() and deviceRemoved() + signals. Event-driven behavior is currently available only on Windows and OS X. + + \b Example + \code + QextSerialEnumerator* enumerator = new QextSerialEnumerator(); + connect(enumerator, SIGNAL(deviceDiscovered(const QextPortInfo &)), + myClass, SLOT(onDeviceDiscovered(const QextPortInfo &))); + connect(enumerator, SIGNAL(deviceRemoved(const QextPortInfo &)), + myClass, SLOT(onDeviceRemoved(const QextPortInfo &))); + \endcode + + \section Credits + Windows implementation is based on Zach Gorman's work from + The Code Project (http://www.codeproject.com/system/setupdi.asp). + + OS X implementation, see + http://developer.apple.com/documentation/DeviceDrivers/Conceptual/AccessingHardware/AH_Finding_Devices/chapter_4_section_2.html + + \author Michal Policht, Liam Staskawicz +*/ +class QEXTSERIALPORT_EXPORT QextSerialEnumerator : public QObject +{ +Q_OBJECT + public: + QextSerialEnumerator( ); + ~QextSerialEnumerator( ); + + #ifdef Q_OS_WIN + LRESULT onDeviceChangeWin( WPARAM wParam, LPARAM lParam ); + private: + /*! + * Get value of specified property from the registry. + * \param key handle to an open key. + * \param property property name. + * \return property value. + */ + static QString getRegKeyValue(HKEY key, LPCTSTR property); + + /*! + * Get specific property from registry. + * \param devInfo pointer to the device information set that contains the interface + * and its underlying device. Returned by SetupDiGetClassDevs() function. + * \param devData pointer to an SP_DEVINFO_DATA structure that defines the device instance. + * this is returned by SetupDiGetDeviceInterfaceDetail() function. + * \param property registry property. One of defined SPDRP_* constants. + * \return property string. + */ + static QString getDeviceProperty(HDEVINFO devInfo, PSP_DEVINFO_DATA devData, DWORD property); + + /*! + * Search for serial ports using setupapi. + * \param infoList list with result. + */ + static void setupAPIScan(QList & infoList); + void setUpNotificationWin( ); + static bool getDeviceDetailsWin( QextPortInfo* portInfo, HDEVINFO devInfo, + PSP_DEVINFO_DATA devData, WPARAM wParam = DBT_DEVICEARRIVAL ); + static void enumerateDevicesWin( const GUID & guidDev, QList* infoList ); + bool matchAndDispatchChangedDevice(const QString & deviceID, const GUID & guid, WPARAM wParam); + #ifdef QT_GUI_LIB + QextSerialRegistrationWidget* notificationWidget; + #endif + #endif /*Q_OS_WIN*/ + + #ifdef Q_OS_UNIX + #ifdef Q_OS_MAC + private: + /*! + * Search for serial ports using IOKit. + * \param infoList list with result. + */ + static void scanPortsOSX(QList & infoList); + static void iterateServicesOSX(io_object_t service, QList & infoList); + static bool getServiceDetailsOSX( io_object_t service, QextPortInfo* portInfo ); + + void setUpNotificationOSX( ); + void onDeviceDiscoveredOSX( io_object_t service ); + void onDeviceTerminatedOSX( io_object_t service ); + friend void deviceDiscoveredCallbackOSX( void *ctxt, io_iterator_t serialPortIterator ); + friend void deviceTerminatedCallbackOSX( void *ctxt, io_iterator_t serialPortIterator ); + + IONotificationPortRef notificationPortRef; + + #else // Q_OS_MAC + private: + /*! + * Search for serial ports on unix. + * \param infoList list with result. + */ + static void scanPortsNix(QList & infoList); + #endif // Q_OS_MAC + #endif /* Q_OS_UNIX */ + + public: + /*! + Get list of ports. + \return list of ports currently available in the system. + */ + static QList getPorts(); + /*! + Enable event-driven notifications of board discovery/removal. + */ + void setUpNotifications( ); + + signals: + /*! + A new device has been connected to the system. + + setUpNotifications() must be called first to enable event-driven device notifications. + Currently only implemented on Windows and OS X. + \param info The device that has been discovered. + */ + void deviceDiscovered( const QextPortInfo & info ); + /*! + A device has been disconnected from the system. + + setUpNotifications() must be called first to enable event-driven device notifications. + Currently only implemented on Windows and OS X. + \param info The device that was disconnected. + */ + void deviceRemoved( const QextPortInfo & info ); +}; + +#endif /*_QEXTSERIALENUMERATOR_H_*/ diff --git a/Sources/qextserialport/qextserialenumerator_osx.cpp b/Sources/qextserialport/qextserialenumerator_osx.cpp new file mode 100644 index 0000000..229d73f --- /dev/null +++ b/Sources/qextserialport/qextserialenumerator_osx.cpp @@ -0,0 +1,288 @@ + + + +#include "qextserialenumerator.h" +#include +#include + +#include +#include +#include + +QextSerialEnumerator::QextSerialEnumerator( ) +{ + if( !QMetaType::isRegistered( QMetaType::type("QextPortInfo") ) ) + qRegisterMetaType("QextPortInfo"); +} + +QextSerialEnumerator::~QextSerialEnumerator( ) +{ + IONotificationPortDestroy( notificationPortRef ); +} + +// static +QList QextSerialEnumerator::getPorts() +{ + QList infoList; + io_iterator_t serialPortIterator = 0; + kern_return_t kernResult = KERN_FAILURE; + CFMutableDictionaryRef matchingDictionary; + + // first try to get any serialbsd devices, then try any USBCDC devices + if( !(matchingDictionary = IOServiceMatching(kIOSerialBSDServiceValue) ) ) { + qWarning("IOServiceMatching returned a NULL dictionary."); + return infoList; + } + CFDictionaryAddValue(matchingDictionary, CFSTR(kIOSerialBSDTypeKey), CFSTR(kIOSerialBSDAllTypes)); + + // then create the iterator with all the matching devices + if( IOServiceGetMatchingServices(kIOMasterPortDefault, matchingDictionary, &serialPortIterator) != KERN_SUCCESS ) { + qCritical() << "IOServiceGetMatchingServices failed, returned" << kernResult; + return infoList; + } + iterateServicesOSX(serialPortIterator, infoList); + IOObjectRelease(serialPortIterator); + serialPortIterator = 0; + + if( !(matchingDictionary = IOServiceNameMatching("AppleUSBCDC")) ) { + qWarning("IOServiceNameMatching returned a NULL dictionary."); + return infoList; + } + + if( IOServiceGetMatchingServices(kIOMasterPortDefault, matchingDictionary, &serialPortIterator) != KERN_SUCCESS ) { + qCritical() << "IOServiceGetMatchingServices failed, returned" << kernResult; + return infoList; + } + iterateServicesOSX(serialPortIterator, infoList); + IOObjectRelease(serialPortIterator); + + return infoList; +} + +void QextSerialEnumerator::iterateServicesOSX(io_object_t service, QList & infoList) +{ + // Iterate through all modems found. + io_object_t usbService; + while( ( usbService = IOIteratorNext(service) ) ) + { + QextPortInfo info; + info.vendorID = 0; + info.productID = 0; + getServiceDetailsOSX( usbService, &info ); + infoList.append(info); + } +} + +bool QextSerialEnumerator::getServiceDetailsOSX( io_object_t service, QextPortInfo* portInfo ) +{ + bool retval = true; + CFTypeRef bsdPathAsCFString = NULL; + CFTypeRef productNameAsCFString = NULL; + CFTypeRef vendorIdAsCFNumber = NULL; + CFTypeRef productIdAsCFNumber = NULL; + // check the name of the modem's callout device + bsdPathAsCFString = IORegistryEntryCreateCFProperty(service, CFSTR(kIOCalloutDeviceKey), + kCFAllocatorDefault, 0); + + // wander up the hierarchy until we find the level that can give us the + // vendor/product IDs and the product name, if available + io_registry_entry_t parent; + kern_return_t kernResult = IORegistryEntryGetParentEntry(service, kIOServicePlane, &parent); + while( kernResult == KERN_SUCCESS && !vendorIdAsCFNumber && !productIdAsCFNumber ) + { + if(!productNameAsCFString) + productNameAsCFString = IORegistryEntrySearchCFProperty(parent, + kIOServicePlane, + CFSTR("Product Name"), + kCFAllocatorDefault, 0); + vendorIdAsCFNumber = IORegistryEntrySearchCFProperty(parent, + kIOServicePlane, + CFSTR(kUSBVendorID), + kCFAllocatorDefault, 0); + productIdAsCFNumber = IORegistryEntrySearchCFProperty(parent, + kIOServicePlane, + CFSTR(kUSBProductID), + kCFAllocatorDefault, 0); + io_registry_entry_t oldparent = parent; + kernResult = IORegistryEntryGetParentEntry(parent, kIOServicePlane, &parent); + IOObjectRelease(oldparent); + } + + io_string_t ioPathName; + IORegistryEntryGetPath( service, kIOServicePlane, ioPathName ); + portInfo->physName = ioPathName; + + if( bsdPathAsCFString ) + { + char path[MAXPATHLEN]; + if( CFStringGetCString((CFStringRef)bsdPathAsCFString, path, + PATH_MAX, kCFStringEncodingUTF8) ) + portInfo->portName = path; + CFRelease(bsdPathAsCFString); + } + + if(productNameAsCFString) + { + char productName[MAXPATHLEN]; + if( CFStringGetCString((CFStringRef)productNameAsCFString, productName, + PATH_MAX, kCFStringEncodingUTF8) ) + portInfo->friendName = productName; + CFRelease(productNameAsCFString); + } + + if(vendorIdAsCFNumber) + { + SInt32 vID; + if(CFNumberGetValue((CFNumberRef)vendorIdAsCFNumber, kCFNumberSInt32Type, &vID)) + portInfo->vendorID = vID; + CFRelease(vendorIdAsCFNumber); + } + + if(productIdAsCFNumber) + { + SInt32 pID; + if(CFNumberGetValue((CFNumberRef)productIdAsCFNumber, kCFNumberSInt32Type, &pID)) + portInfo->productID = pID; + CFRelease(productIdAsCFNumber); + } + IOObjectRelease(service); + return retval; +} + +// IOKit callbacks registered via setupNotifications() +void deviceDiscoveredCallbackOSX( void *ctxt, io_iterator_t serialPortIterator ); +void deviceTerminatedCallbackOSX( void *ctxt, io_iterator_t serialPortIterator ); + +void deviceDiscoveredCallbackOSX( void *ctxt, io_iterator_t serialPortIterator ) +{ + QextSerialEnumerator* qese = (QextSerialEnumerator*)ctxt; + io_object_t serialService; + while ((serialService = IOIteratorNext(serialPortIterator))) + qese->onDeviceDiscoveredOSX(serialService); +} + +void deviceTerminatedCallbackOSX( void *ctxt, io_iterator_t serialPortIterator ) +{ + QextSerialEnumerator* qese = (QextSerialEnumerator*)ctxt; + io_object_t serialService; + while ((serialService = IOIteratorNext(serialPortIterator))) + qese->onDeviceTerminatedOSX(serialService); +} + +/* + A device has been discovered via IOKit. + Create a QextPortInfo if possible, and emit the signal indicating that we've found it. +*/ +void QextSerialEnumerator::onDeviceDiscoveredOSX( io_object_t service ) +{ + QextPortInfo info; + info.vendorID = 0; + info.productID = 0; + if( getServiceDetailsOSX( service, &info ) ) + emit deviceDiscovered( info ); +} + +/* + Notification via IOKit that a device has been removed. + Create a QextPortInfo if possible, and emit the signal indicating that it's gone. +*/ +void QextSerialEnumerator::onDeviceTerminatedOSX( io_object_t service ) +{ + QextPortInfo info; + info.vendorID = 0; + info.productID = 0; + if( getServiceDetailsOSX( service, &info ) ) + emit deviceRemoved( info ); +} + +/* + Create matching dictionaries for the devices we want to get notifications for, + and add them to the current run loop. Invoke the callbacks that will be responding + to these notifications once to arm them, and discover any devices that + are currently connected at the time notifications are setup. +*/ +void QextSerialEnumerator::setUpNotifications( ) +{ + kern_return_t kernResult; + mach_port_t masterPort; + CFRunLoopSourceRef notificationRunLoopSource; + CFMutableDictionaryRef classesToMatch; + CFMutableDictionaryRef cdcClassesToMatch; + io_iterator_t portIterator; + + kernResult = IOMasterPort(MACH_PORT_NULL, &masterPort); + if (KERN_SUCCESS != kernResult) { + qDebug() << "IOMasterPort returned:" << kernResult; + return; + } + + classesToMatch = IOServiceMatching(kIOSerialBSDServiceValue); + if (classesToMatch == NULL) + qDebug("IOServiceMatching returned a NULL dictionary."); + else + CFDictionarySetValue(classesToMatch, CFSTR(kIOSerialBSDTypeKey), CFSTR(kIOSerialBSDAllTypes)); + + if( !(cdcClassesToMatch = IOServiceNameMatching("AppleUSBCDC") ) ) { + qWarning("couldn't create cdc matching dict"); + return; + } + + // Retain an additional reference since each call to IOServiceAddMatchingNotification consumes one. + classesToMatch = (CFMutableDictionaryRef) CFRetain(classesToMatch); + cdcClassesToMatch = (CFMutableDictionaryRef) CFRetain(cdcClassesToMatch); + + notificationPortRef = IONotificationPortCreate(masterPort); + if(notificationPortRef == NULL) { + qDebug("IONotificationPortCreate return a NULL IONotificationPortRef."); + return; + } + + notificationRunLoopSource = IONotificationPortGetRunLoopSource(notificationPortRef); + if (notificationRunLoopSource == NULL) { + qDebug("IONotificationPortGetRunLoopSource returned NULL CFRunLoopSourceRef."); + return; + } + + CFRunLoopAddSource(CFRunLoopGetCurrent(), notificationRunLoopSource, kCFRunLoopDefaultMode); + + kernResult = IOServiceAddMatchingNotification(notificationPortRef, kIOMatchedNotification, classesToMatch, + deviceDiscoveredCallbackOSX, this, &portIterator); + if (kernResult != KERN_SUCCESS) { + qDebug() << "IOServiceAddMatchingNotification return:" << kernResult; + return; + } + + // arm the callback, and grab any devices that are already connected + deviceDiscoveredCallbackOSX( this, portIterator ); + + kernResult = IOServiceAddMatchingNotification(notificationPortRef, kIOMatchedNotification, cdcClassesToMatch, + deviceDiscoveredCallbackOSX, this, &portIterator); + if (kernResult != KERN_SUCCESS) { + qDebug() << "IOServiceAddMatchingNotification return:" << kernResult; + return; + } + + // arm the callback, and grab any devices that are already connected + deviceDiscoveredCallbackOSX( this, portIterator ); + + kernResult = IOServiceAddMatchingNotification(notificationPortRef, kIOTerminatedNotification, classesToMatch, + deviceTerminatedCallbackOSX, this, &portIterator); + if (kernResult != KERN_SUCCESS) { + qDebug() << "IOServiceAddMatchingNotification return:" << kernResult; + return; + } + + // arm the callback, and clear any devices that are terminated + deviceTerminatedCallbackOSX( this, portIterator ); + + kernResult = IOServiceAddMatchingNotification(notificationPortRef, kIOTerminatedNotification, cdcClassesToMatch, + deviceTerminatedCallbackOSX, this, &portIterator); + if (kernResult != KERN_SUCCESS) { + qDebug() << "IOServiceAddMatchingNotification return:" << kernResult; + return; + } + + // arm the callback, and clear any devices that are terminated + deviceTerminatedCallbackOSX( this, portIterator ); +} + diff --git a/Sources/qextserialport/qextserialenumerator_unix.cpp b/Sources/qextserialport/qextserialenumerator_unix.cpp new file mode 100644 index 0000000..f43a75b --- /dev/null +++ b/Sources/qextserialport/qextserialenumerator_unix.cpp @@ -0,0 +1,75 @@ + + + +#include "qextserialenumerator.h" +#include +#include +#include +#include + +QextSerialEnumerator::QextSerialEnumerator( ) +{ + if( !QMetaType::isRegistered( QMetaType::type("QextPortInfo") ) ) + qRegisterMetaType("QextPortInfo"); +} + +QextSerialEnumerator::~QextSerialEnumerator( ) +{ +} + +QList QextSerialEnumerator::getPorts() +{ + QList infoList; +#ifdef Q_OS_LINUX + QStringList portNamePrefixes, portNameList; + portNamePrefixes << "ttyS*"; // list normal serial ports first + + QDir dir("/dev"); + portNameList = dir.entryList(portNamePrefixes, (QDir::System | QDir::Files), QDir::Name); + + // remove the values which are not serial ports for e.g. /dev/ttysa + for (int i = 0; i < portNameList.size(); i++) { + bool ok; + QString current = portNameList.at(i); + // remove the ttyS part, and check, if the other part is a number + current.remove(0,4).toInt(&ok, 10); + if (!ok) { + portNameList.removeAt(i); + i--; + } + } + + // get the non standard serial ports names + // (USB-serial, bluetooth-serial, 18F PICs, and so on) + // if you know an other name prefix for serial ports please let us know + portNamePrefixes.clear(); + portNamePrefixes << "ttyACM*" << "ttyUSB*" << "rfcomm*"; + portNameList.append(dir.entryList(portNamePrefixes, (QDir::System | QDir::Files), QDir::Name)); + + foreach (QString str , portNameList) { + QextPortInfo inf; + inf.physName = "/dev/"+str; + inf.portName = str; + + if (str.contains("ttyS")) { + inf.friendName = "Serial port "+str.remove(0, 4); + } + else if (str.contains("ttyUSB")) { + inf.friendName = "USB-serial adapter "+str.remove(0, 6); + } + else if (str.contains("rfcomm")) { + inf.friendName = "Bluetooth-serial adapter "+str.remove(0, 6); + } + inf.enumName = "/dev"; // is there a more helpful name for this? + infoList.append(inf); + } +#else + qCritical("Enumeration for POSIX systems (except Linux) is not implemented yet."); +#endif + return infoList; +} + +void QextSerialEnumerator::setUpNotifications( ) +{ + qCritical("Notifications for *Nix/FreeBSD are not implemented yet"); +} diff --git a/Sources/qextserialport/qextserialenumerator_win.cpp b/Sources/qextserialport/qextserialenumerator_win.cpp new file mode 100644 index 0000000..e2ef78c --- /dev/null +++ b/Sources/qextserialport/qextserialenumerator_win.cpp @@ -0,0 +1,206 @@ + + + +#include "qextserialenumerator.h" +#include +#include + +#include +#include +#include "qextserialport.h" +#include + +QextSerialEnumerator::QextSerialEnumerator( ) +{ + if( !QMetaType::isRegistered( QMetaType::type("QextPortInfo") ) ) + qRegisterMetaType("QextPortInfo"); +#if (defined QT_GUI_LIB) + notificationWidget = 0; +#endif // Q_OS_WIN +} + +QextSerialEnumerator::~QextSerialEnumerator( ) +{ +#if (defined QT_GUI_LIB) + if( notificationWidget ) + delete notificationWidget; +#endif +} + + + +// see http://msdn.microsoft.com/en-us/library/ms791134.aspx for list of GUID classes +#ifndef GUID_DEVCLASS_PORTS + DEFINE_GUID(GUID_DEVCLASS_PORTS, 0x4D36E978, 0xE325, 0x11CE, 0xBF, 0xC1, 0x08, 0x00, 0x2B, 0xE1, 0x03, 0x18 ); +#endif + +/* Gordon Schumacher's macros for TCHAR -> QString conversions and vice versa */ +#ifdef UNICODE + #define QStringToTCHAR(x) (wchar_t*) x.utf16() + #define PQStringToTCHAR(x) (wchar_t*) x->utf16() + #define TCHARToQString(x) QString::fromUtf16((ushort*)(x)) + #define TCHARToQStringN(x,y) QString::fromUtf16((ushort*)(x),(y)) +#else + #define QStringToTCHAR(x) x.local8Bit().constData() + #define PQStringToTCHAR(x) x->local8Bit().constData() + #define TCHARToQString(x) QString::fromLocal8Bit((x)) + #define TCHARToQStringN(x,y) QString::fromLocal8Bit((x),(y)) +#endif /*UNICODE*/ + + +//static +QString QextSerialEnumerator::getRegKeyValue(HKEY key, LPCTSTR property) +{ + DWORD size = 0; + DWORD type; + RegQueryValueEx(key, property, NULL, NULL, NULL, & size); + BYTE* buff = new BYTE[size]; + QString result; + if( RegQueryValueEx(key, property, NULL, &type, buff, & size) == ERROR_SUCCESS ) + result = TCHARToQString((const char *)buff); + RegCloseKey(key); + delete [] buff; + return result; +} + +//static +QString QextSerialEnumerator::getDeviceProperty(HDEVINFO devInfo, PSP_DEVINFO_DATA devData, DWORD property) +{ + DWORD buffSize = 0; + SetupDiGetDeviceRegistryProperty(devInfo, devData, property, NULL, NULL, 0, & buffSize); + BYTE* buff = new BYTE[buffSize]; + SetupDiGetDeviceRegistryProperty(devInfo, devData, property, NULL, buff, buffSize, NULL); + QString result = TCHARToQString((const char *)buff); + delete [] buff; + return result; +} + +QList QextSerialEnumerator::getPorts() +{ + QList ports; + enumerateDevicesWin(GUID_DEVCLASS_PORTS, &ports); + return ports; +} + +void QextSerialEnumerator::enumerateDevicesWin( const GUID & guid, QList* infoList ) +{ + HDEVINFO devInfo; + if( (devInfo = SetupDiGetClassDevs(&guid, NULL, NULL, DIGCF_PRESENT)) != INVALID_HANDLE_VALUE) + { + SP_DEVINFO_DATA devInfoData; + devInfoData.cbSize = sizeof(SP_DEVINFO_DATA); + for(int i = 0; SetupDiEnumDeviceInfo(devInfo, i, &devInfoData); i++) + { + QextPortInfo info; + info.productID = info.vendorID = 0; + getDeviceDetailsWin( &info, devInfo, &devInfoData ); + infoList->append(info); + } + SetupDiDestroyDeviceInfoList(devInfo); + } +} + +#ifdef QT_GUI_LIB +bool QextSerialRegistrationWidget::winEvent( MSG* message, long* result ) +{ + if ( message->message == WM_DEVICECHANGE ) { + qese->onDeviceChangeWin( message->wParam, message->lParam ); + *result = 1; + return true; + } + return false; +} +#endif + +void QextSerialEnumerator::setUpNotifications( ) +{ + #ifdef QT_GUI_LIB + if(notificationWidget) + return; + notificationWidget = new QextSerialRegistrationWidget(this); + + DEV_BROADCAST_DEVICEINTERFACE dbh; + ZeroMemory(&dbh, sizeof(dbh)); + dbh.dbcc_size = sizeof(dbh); + dbh.dbcc_devicetype = DBT_DEVTYP_DEVICEINTERFACE; + CopyMemory(&dbh.dbcc_classguid, &GUID_DEVCLASS_PORTS, sizeof(GUID)); + if( RegisterDeviceNotification( notificationWidget->winId( ), &dbh, DEVICE_NOTIFY_WINDOW_HANDLE ) == NULL) + qWarning() << "RegisterDeviceNotification failed:" << GetLastError(); + // setting up notifications doesn't tell us about devices already connected + // so get those manually + foreach( QextPortInfo port, getPorts() ) + emit deviceDiscovered( port ); + #else + qWarning("QextSerialEnumerator: GUI not enabled - can't register for device notifications."); + #endif // QT_GUI_LIB +} + +LRESULT QextSerialEnumerator::onDeviceChangeWin( WPARAM wParam, LPARAM lParam ) +{ + if ( DBT_DEVICEARRIVAL == wParam || DBT_DEVICEREMOVECOMPLETE == wParam ) + { + PDEV_BROADCAST_HDR pHdr = (PDEV_BROADCAST_HDR)lParam; + if( pHdr->dbch_devicetype == DBT_DEVTYP_DEVICEINTERFACE ) + { + PDEV_BROADCAST_DEVICEINTERFACE pDevInf = (PDEV_BROADCAST_DEVICEINTERFACE)pHdr; + // delimiters are different across APIs...change to backslash. ugh. + QString deviceID = TCHARToQString(pDevInf->dbcc_name).toUpper().replace("#", "\\"); + + matchAndDispatchChangedDevice(deviceID, GUID_DEVCLASS_PORTS, wParam); + } + } + return 0; +} + +bool QextSerialEnumerator::matchAndDispatchChangedDevice(const QString & deviceID, const GUID & guid, WPARAM wParam) +{ + bool rv = false; + DWORD dwFlag = (DBT_DEVICEARRIVAL == wParam) ? DIGCF_PRESENT : DIGCF_ALLCLASSES; + HDEVINFO devInfo; + if( (devInfo = SetupDiGetClassDevs(&guid,NULL,NULL,dwFlag)) != INVALID_HANDLE_VALUE ) + { + SP_DEVINFO_DATA spDevInfoData; + spDevInfoData.cbSize = sizeof(SP_DEVINFO_DATA); + for(int i=0; SetupDiEnumDeviceInfo(devInfo, i, &spDevInfoData); i++) + { + DWORD nSize=0 ; + TCHAR buf[MAX_PATH]; + if ( SetupDiGetDeviceInstanceId(devInfo, &spDevInfoData, buf, MAX_PATH, &nSize) && + deviceID.contains(TCHARToQString(buf))) // we found a match + { + rv = true; + QextPortInfo info; + info.productID = info.vendorID = 0; + getDeviceDetailsWin( &info, devInfo, &spDevInfoData, wParam ); + if( wParam == DBT_DEVICEARRIVAL ) + emit deviceDiscovered(info); + else if( wParam == DBT_DEVICEREMOVECOMPLETE ) + emit deviceRemoved(info); + break; + } + } + SetupDiDestroyDeviceInfoList(devInfo); + } + return rv; +} + +bool QextSerialEnumerator::getDeviceDetailsWin( QextPortInfo* portInfo, HDEVINFO devInfo, PSP_DEVINFO_DATA devData, WPARAM wParam ) +{ + portInfo->friendName = getDeviceProperty(devInfo, devData, SPDRP_FRIENDLYNAME); + if( wParam == DBT_DEVICEARRIVAL) + portInfo->physName = getDeviceProperty(devInfo, devData, SPDRP_PHYSICAL_DEVICE_OBJECT_NAME); + portInfo->enumName = getDeviceProperty(devInfo, devData, SPDRP_ENUMERATOR_NAME); + QString hardwareIDs = getDeviceProperty(devInfo, devData, SPDRP_HARDWAREID); + HKEY devKey = SetupDiOpenDevRegKey(devInfo, devData, DICS_FLAG_GLOBAL, 0, DIREG_DEV, KEY_READ); + portInfo->portName = QextSerialPort::fullPortNameWin( getRegKeyValue(devKey, TEXT("PortName")) ); + QRegExp idRx("VID_(\\w+)&PID_(\\w+)"); + if( hardwareIDs.toUpper().contains(idRx) ) + { + bool dummy; + portInfo->vendorID = idRx.cap(1).toInt(&dummy, 16); + portInfo->productID = idRx.cap(2).toInt(&dummy, 16); + //qDebug() << "got vid:" << vid << "pid:" << pid; + } + return true; +} +