Mercurial > hg > octave-lyh
diff gui/src/kptydevice.cpp @ 13495:11e03a76d8c0
Added source files.
author | Jacob Dawid <jacob.dawid@googlemail.com> |
---|---|
date | Fri, 29 Apr 2011 23:51:33 +0200 |
parents | |
children |
line wrap: on
line diff
new file mode 100644 --- /dev/null +++ b/gui/src/kptydevice.cpp @@ -0,0 +1,413 @@ +/* + + This file is part of the KDE libraries + Copyright (C) 2007 Oswald Buddenhagen <ossi@kde.org> + Copyright (C) 2010 KDE e.V. <kde-ev-board@kde.org> + Author Adriaan de Groot <groot@kde.org> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include "kptydevice.h" +#include "kpty_p.h" +#define i18n + +#include <QtCore/QSocketNotifier> + +#include <unistd.h> +#include <errno.h> +#include <signal.h> +#include <termios.h> +#include <fcntl.h> +#include <sys/ioctl.h> +#ifdef HAVE_SYS_FILIO_H +# include <sys/filio.h> +#endif +#ifdef HAVE_SYS_TIME_H +# include <sys/time.h> +#endif + +#if defined(Q_OS_FREEBSD) || defined(Q_OS_MAC) + // "the other end's output queue size" - kinda braindead, huh? +# define PTY_BYTES_AVAILABLE TIOCOUTQ +#elif defined(TIOCINQ) + // "our end's input queue size" +# define PTY_BYTES_AVAILABLE TIOCINQ +#else + // likewise. more generic ioctl (theoretically) +# define PTY_BYTES_AVAILABLE FIONREAD +#endif + +////////////////// +// private data // +////////////////// + +// Lifted from Qt. I don't think they would mind. ;) +// Re-lift again from Qt whenever a proper replacement for pthread_once appears +static void qt_ignore_sigpipe() +{ + static QBasicAtomicInt atom = Q_BASIC_ATOMIC_INITIALIZER(0); + if (atom.testAndSetRelaxed(0, 1)) { + struct sigaction noaction; + memset(&noaction, 0, sizeof(noaction)); + noaction.sa_handler = SIG_IGN; + sigaction(SIGPIPE, &noaction, 0); + } +} + +#define NO_INTR(ret,func) do { ret = func; } while (ret < 0 && errno == EINTR) + +bool KPtyDevicePrivate::_k_canRead() +{ + Q_Q(KPtyDevice); + qint64 readBytes = 0; + +#ifdef Q_OS_IRIX // this should use a config define, but how to check it? + size_t available; +#else + int available; +#endif + if (!::ioctl(q->masterFd(), PTY_BYTES_AVAILABLE, (char *) &available)) { +#ifdef Q_OS_SOLARIS + // A Pty is a STREAMS module, and those can be activated + // with 0 bytes available. This happens either when ^C is + // pressed, or when an application does an explicit write(a,b,0) + // which happens in experiments fairly often. When 0 bytes are + // available, you must read those 0 bytes to clear the STREAMS + // module, but we don't want to hit the !readBytes case further down. + if (!available) { + char c; + // Read the 0-byte STREAMS message + NO_INTR(readBytes, read(q->masterFd(), &c, 0)); + // Should return 0 bytes read; -1 is error + if (readBytes < 0) { + readNotifier->setEnabled(false); + emit q->readEof(); + return false; + } + return true; + } +#endif + + char *ptr = readBuffer.reserve(available); +#ifdef Q_OS_SOLARIS + // Even if available > 0, it is possible for read() + // to return 0 on Solaris, due to 0-byte writes in the stream. + // Ignore them and keep reading until we hit *some* data. + // In Solaris it is possible to have 15 bytes available + // and to (say) get 0, 0, 6, 0 and 9 bytes in subsequent reads. + // Because the stream is set to O_NONBLOCK in finishOpen(), + // an EOF read will return -1. + readBytes = 0; + while (!readBytes) +#endif + // Useless block braces except in Solaris + { + NO_INTR(readBytes, read(q->masterFd(), ptr, available)); + } + if (readBytes < 0) { + readBuffer.unreserve(available); + q->setErrorString(i18n("Error reading from PTY")); + return false; + } + readBuffer.unreserve(available - readBytes); // *should* be a no-op + } + + if (!readBytes) { + readNotifier->setEnabled(false); + emit q->readEof(); + return false; + } else { + if (!emittedReadyRead) { + emittedReadyRead = true; + emit q->readyRead(); + emittedReadyRead = false; + } + return true; + } +} + +bool KPtyDevicePrivate::_k_canWrite() +{ + Q_Q(KPtyDevice); + + writeNotifier->setEnabled(false); + if (writeBuffer.isEmpty()) + return false; + + qt_ignore_sigpipe(); + int wroteBytes; + NO_INTR(wroteBytes, + write(q->masterFd(), + writeBuffer.readPointer(), writeBuffer.readSize())); + if (wroteBytes < 0) { + q->setErrorString(i18n("Error writing to PTY")); + return false; + } + writeBuffer.free(wroteBytes); + + if (!emittedBytesWritten) { + emittedBytesWritten = true; + emit q->bytesWritten(wroteBytes); + emittedBytesWritten = false; + } + + if (!writeBuffer.isEmpty()) + writeNotifier->setEnabled(true); + return true; +} + +#ifndef timeradd +// Lifted from GLIBC +# define timeradd(a, b, result) \ + do { \ + (result)->tv_sec = (a)->tv_sec + (b)->tv_sec; \ + (result)->tv_usec = (a)->tv_usec + (b)->tv_usec; \ + if ((result)->tv_usec >= 1000000) { \ + ++(result)->tv_sec; \ + (result)->tv_usec -= 1000000; \ + } \ + } while (0) +# define timersub(a, b, result) \ + do { \ + (result)->tv_sec = (a)->tv_sec - (b)->tv_sec; \ + (result)->tv_usec = (a)->tv_usec - (b)->tv_usec; \ + if ((result)->tv_usec < 0) { \ + --(result)->tv_sec; \ + (result)->tv_usec += 1000000; \ + } \ + } while (0) +#endif + +bool KPtyDevicePrivate::doWait(int msecs, bool reading) +{ + Q_Q(KPtyDevice); +#ifndef __linux__ + struct timeval etv; +#endif + struct timeval tv, *tvp; + + if (msecs < 0) + tvp = 0; + else { + tv.tv_sec = msecs / 1000; + tv.tv_usec = (msecs % 1000) * 1000; +#ifndef __linux__ + gettimeofday(&etv, 0); + timeradd(&tv, &etv, &etv); +#endif + tvp = &tv; + } + + while (reading ? readNotifier->isEnabled() : !writeBuffer.isEmpty()) { + fd_set rfds; + fd_set wfds; + + FD_ZERO(&rfds); + FD_ZERO(&wfds); + + if (readNotifier->isEnabled()) + FD_SET(q->masterFd(), &rfds); + if (!writeBuffer.isEmpty()) + FD_SET(q->masterFd(), &wfds); + +#ifndef __linux__ + if (tvp) { + gettimeofday(&tv, 0); + timersub(&etv, &tv, &tv); + if (tv.tv_sec < 0) + tv.tv_sec = tv.tv_usec = 0; + } +#endif + + switch (select(q->masterFd() + 1, &rfds, &wfds, 0, tvp)) { + case -1: + if (errno == EINTR) + break; + return false; + case 0: + q->setErrorString(i18n("PTY operation timed out")); + return false; + default: + if (FD_ISSET(q->masterFd(), &rfds)) { + bool canRead = _k_canRead(); + if (reading && canRead) + return true; + } + if (FD_ISSET(q->masterFd(), &wfds)) { + bool canWrite = _k_canWrite(); + if (!reading) + return canWrite; + } + break; + } + } + return false; +} + +void KPtyDevicePrivate::finishOpen(QIODevice::OpenMode mode) +{ + Q_Q(KPtyDevice); + + q->QIODevice::open(mode); + fcntl(q->masterFd(), F_SETFL, O_NONBLOCK); + readBuffer.clear(); + readNotifier = new QSocketNotifier(q->masterFd(), QSocketNotifier::Read, q); + writeNotifier = new QSocketNotifier(q->masterFd(), QSocketNotifier::Write, q); + QObject::connect(readNotifier, SIGNAL(activated(int)), q, SLOT(_k_canRead())); + QObject::connect(writeNotifier, SIGNAL(activated(int)), q, SLOT(_k_canWrite())); + readNotifier->setEnabled(true); +} + +///////////////////////////// +// public member functions // +///////////////////////////// + +KPtyDevice::KPtyDevice(QObject *parent) : + QIODevice(parent), + KPty(new KPtyDevicePrivate(this)) +{ +} + +KPtyDevice::~KPtyDevice() +{ + close(); +} + +bool KPtyDevice::open(OpenMode mode) +{ + Q_D(KPtyDevice); + + if (masterFd() >= 0) + return true; + + if (!KPty::open()) { + setErrorString(i18n("Error opening PTY")); + return false; + } + + d->finishOpen(mode); + + return true; +} + +bool KPtyDevice::open(int fd, OpenMode mode) +{ + Q_D(KPtyDevice); + + if (!KPty::open(fd)) { + setErrorString(i18n("Error opening PTY")); + return false; + } + + d->finishOpen(mode); + + return true; +} + +void KPtyDevice::close() +{ + Q_D(KPtyDevice); + + if (masterFd() < 0) + return; + + delete d->readNotifier; + delete d->writeNotifier; + + QIODevice::close(); + + KPty::close(); +} + +bool KPtyDevice::isSequential() const +{ + return true; +} + +bool KPtyDevice::canReadLine() const +{ + Q_D(const KPtyDevice); + return QIODevice::canReadLine() || d->readBuffer.canReadLine(); +} + +bool KPtyDevice::atEnd() const +{ + Q_D(const KPtyDevice); + return QIODevice::atEnd() && d->readBuffer.isEmpty(); +} + +qint64 KPtyDevice::bytesAvailable() const +{ + Q_D(const KPtyDevice); + return QIODevice::bytesAvailable() + d->readBuffer.size(); +} + +qint64 KPtyDevice::bytesToWrite() const +{ + Q_D(const KPtyDevice); + return d->writeBuffer.size(); +} + +bool KPtyDevice::waitForReadyRead(int msecs) +{ + Q_D(KPtyDevice); + return d->doWait(msecs, true); +} + +bool KPtyDevice::waitForBytesWritten(int msecs) +{ + Q_D(KPtyDevice); + return d->doWait(msecs, false); +} + +void KPtyDevice::setSuspended(bool suspended) +{ + Q_D(KPtyDevice); + d->readNotifier->setEnabled(!suspended); +} + +bool KPtyDevice::isSuspended() const +{ + Q_D(const KPtyDevice); + return !d->readNotifier->isEnabled(); +} + +// protected +qint64 KPtyDevice::readData(char *data, qint64 maxlen) +{ + Q_D(KPtyDevice); + return d->readBuffer.read(data, (int)qMin<qint64>(maxlen, KMAXINT)); +} + +// protected +qint64 KPtyDevice::readLineData(char *data, qint64 maxlen) +{ + Q_D(KPtyDevice); + return d->readBuffer.readLine(data, (int)qMin<qint64>(maxlen, KMAXINT)); +} + +// protected +qint64 KPtyDevice::writeData(const char *data, qint64 len) +{ + Q_D(KPtyDevice); + Q_ASSERT(len <= KMAXINT); + + d->writeBuffer.write(data, len); + d->writeNotifier->setEnabled(true); + return len; +} +