├── .gitignore ├── Demo ├── Demo.pro └── main.cpp ├── LICENSE ├── README.md ├── qconsole.cpp ├── qconsole.h ├── qconsole.pri ├── readthread_win.cpp └── readthread_win.h /.gitignore: -------------------------------------------------------------------------------- 1 | # C++ objects and libs 2 | 3 | *.slo 4 | *.lo 5 | *.o 6 | *.a 7 | *.la 8 | *.lai 9 | *.so 10 | *.dll 11 | *.dylib 12 | 13 | # Qt-es 14 | 15 | /.qmake.cache 16 | /.qmake.stash 17 | *.pro.user 18 | *.pro.user.* 19 | *.qbs.user 20 | *.qbs.user.* 21 | *.moc 22 | moc_*.cpp 23 | moc_*.h 24 | qrc_*.cpp 25 | ui_*.h 26 | Makefile* 27 | *build-* 28 | 29 | # QtCreator 30 | 31 | *.autosave 32 | 33 | # QtCtreator Qml 34 | *.qmlproject.user 35 | *.qmlproject.user.* 36 | 37 | # QtCtreator CMake 38 | CMakeLists.txt.user* 39 | 40 | -------------------------------------------------------------------------------- /Demo/Demo.pro: -------------------------------------------------------------------------------- 1 | TEMPLATE = app 2 | 3 | QT += core 4 | QT -= gui 5 | 6 | CONFIG += c++11 console 7 | CONFIG -= app_bundle 8 | 9 | TARGET = Demo 10 | 11 | DEFINES += QT_DEPRECATED_WARNINGS QT_ASCII_CAST_WARNINGS 12 | 13 | include(../qconsole.pri) 14 | 15 | SOURCES += main.cpp 16 | -------------------------------------------------------------------------------- /Demo/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | int main(int argc, char *argv[]) 7 | { 8 | QCoreApplication a(argc, argv); 9 | 10 | auto out = QConsole::qStdOut(&a); 11 | 12 | QConsole console; 13 | QObject::connect(&console, &QConsole::readyRead, [&](){ 14 | auto data = console.read(console.bytesAvailable()); 15 | out->write("Echo: " + data); 16 | if(data.contains("exit")) 17 | qApp->quit(); 18 | }); 19 | QObject::connect(&console, &QConsole::readChannelFinished, 20 | qApp, &QCoreApplication::quit); 21 | if(!console.open()) { 22 | qCritical() << console.errorString(); 23 | return EXIT_FAILURE; 24 | } 25 | out->write("Ready to go!\n"); 26 | out->flush(); 27 | return a.exec(); 28 | } 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2017, Felix Barz 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | * Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # QConsole 2 | A non-blocking Qt Style console class to easily handle stdin/out/err 3 | 4 | ## Features 5 | - Based on QIODevice to allow easy integration 6 | - Read from stdin via Qt without blocking the console 7 | - Uses socket notifiers on UNIX 8 | - Uses a parallel read thread on windows 9 | - Shortcut methods to create QFile instances to stdin/out/err 10 | 11 | ## Installation 12 | The package is provided via qdep, as `Skycoder42/QConsole`. To use it simply: 13 | 14 | 1. Install and enable qdep (See [qdep - Installing](https://github.com/Skycoder42/qdep#installation)) 15 | 2. Add the following to your pro file: 16 | ```qmake 17 | QDEP_DEPENDS += Skycoder42/QConsole 18 | !load(qdep):error("Failed to load qdep feature! Run 'qdep.py prfgen --qmake $$QMAKE_QMAKE' to create it.") 19 | ``` 20 | 21 | ## Example 22 | Usage of this package is fairly simple. Create a consol instance and use it as any other QIODevice. The following is basically the demo, a small app the echoes anything you enter: 23 | 24 | ```cpp 25 | auto out = QConsole::qStdOut(&a); 26 | QConsole console; 27 | QObject::connect(&console, &QConsole::readyRead, [&](){ 28 | auto data = console.read(console.bytesAvailable()); 29 | out->write("Echo: " + data); 30 | if(data.contains("exit")) 31 | qApp->quit(); 32 | }); 33 | console.open(); 34 | ``` -------------------------------------------------------------------------------- /qconsole.cpp: -------------------------------------------------------------------------------- 1 | #include "qconsole.h" 2 | #include 3 | #include 4 | #ifdef Q_OS_WIN 5 | #include "readthread_win.h" 6 | #else 7 | #include 8 | #endif 9 | 10 | QConsole::QConsole(QObject *parent) : 11 | QIODevice(parent), 12 | #ifdef Q_OS_WIN 13 | _readThread{new ReadThread{this}} 14 | #else 15 | _notifier{new QSocketNotifier{fileno(stdin), QSocketNotifier::Read, this}}, 16 | _in{new QFile{this}} 17 | #endif 18 | { 19 | #ifdef Q_OS_WIN 20 | connect(_readThread, &ReadThread::newData, 21 | this, &QConsole::readyRead); 22 | connect(_readThread, &ReadThread::eofTriggered, 23 | this, &QConsole::readChannelFinished); 24 | #else 25 | _in->setObjectName(QStringLiteral("stdin")); 26 | _notifier->setEnabled(false); 27 | connect(_notifier, &QSocketNotifier::activated, 28 | this, &QConsole::activated); 29 | #endif 30 | } 31 | 32 | QConsole::~QConsole() 33 | { 34 | if(isOpen()) 35 | QConsole::close(); 36 | } 37 | 38 | bool QConsole::isSequential() const 39 | { 40 | return true; 41 | } 42 | 43 | bool QConsole::open() 44 | { 45 | if(QIODevice::open(ReadOnly | Unbuffered)) { 46 | #ifdef Q_OS_WIN 47 | _readThread->start(); 48 | #else 49 | if(!_in->open(stdin, ReadOnly | Unbuffered)) { 50 | close(); 51 | setErrorString(QStringLiteral("stdin: %2").arg(_in->errorString())); 52 | return false; 53 | } 54 | _notifier->setEnabled(true); 55 | #endif 56 | return true; 57 | } else 58 | return false; 59 | } 60 | 61 | void QConsole::close() 62 | { 63 | if(isOpen()) { 64 | #ifdef Q_OS_WIN 65 | _readThread->stop(); 66 | #else 67 | _notifier->setEnabled(false); 68 | if(_in->isOpen()) 69 | _in->close(); 70 | #endif 71 | } 72 | QIODevice::close(); 73 | } 74 | 75 | qint64 QConsole::bytesAvailable() const 76 | { 77 | #ifdef Q_OS_WIN 78 | return QIODevice::bytesAvailable() + _readThread->buffer()->bytesAvailable(); 79 | #else 80 | return QIODevice::bytesAvailable() + nBytes(); 81 | #endif 82 | } 83 | 84 | QFile *QConsole::qStdOut(QObject *parent) 85 | { 86 | auto file = new QFile{parent}; 87 | file->open(stdout, QIODevice::WriteOnly | QIODevice::Unbuffered); 88 | return file; 89 | } 90 | 91 | QFile *QConsole::qStdErr(QObject *parent) 92 | { 93 | auto file = new QFile{parent}; 94 | file->open(stderr, QIODevice::WriteOnly | QIODevice::Unbuffered); 95 | return file; 96 | } 97 | 98 | QFile *QConsole::qStdIn(QObject *parent) 99 | { 100 | auto file = new QFile{parent}; 101 | file->open(stdin, QIODevice::ReadOnly | QIODevice::Unbuffered); 102 | return file; 103 | } 104 | 105 | qint64 QConsole::readData(char *data, qint64 maxlen) 106 | { 107 | if(maxlen == 0) 108 | return 0; 109 | #ifdef Q_OS_WIN 110 | return _readThread->buffer()->read(data, maxlen); 111 | #else 112 | auto res = _in->read(data, maxlen); 113 | _notifier->setEnabled(true); 114 | return res; 115 | #endif 116 | } 117 | 118 | qint64 QConsole::writeData(const char *data, qint64 len) 119 | { 120 | Q_UNUSED(data) 121 | Q_UNUSED(len) 122 | return 0; 123 | } 124 | 125 | #ifdef Q_OS_UNIX 126 | void QConsole::activated() 127 | { 128 | _notifier->setEnabled(false); 129 | if(nBytes() <= 0) 130 | emit readChannelFinished(); 131 | else 132 | emit readyRead(); 133 | } 134 | #endif 135 | 136 | bool QConsole::open(QIODevice::OpenMode openMode) 137 | { 138 | Q_UNUSED(openMode) 139 | return open(); 140 | } 141 | 142 | #ifdef Q_OS_UNIX 143 | qint64 QConsole::nBytes() const 144 | { 145 | int n = -1; 146 | if (ioctl(0, FIONREAD, &n) < 0) { 147 | qWarning().noquote() << "ioctl failed with error:" 148 | << qt_error_string(); 149 | return -1; 150 | } else 151 | return static_cast(n); 152 | } 153 | #endif 154 | -------------------------------------------------------------------------------- /qconsole.h: -------------------------------------------------------------------------------- 1 | #ifndef QCONSOLE_H 2 | #define QCONSOLE_H 3 | 4 | #include 5 | class QFile; 6 | #ifdef Q_OS_WIN 7 | class ReadThread; 8 | #else 9 | #include 10 | #endif 11 | 12 | class Q_CONSOLE_EXPORT QConsole : public QIODevice 13 | { 14 | Q_OBJECT 15 | 16 | public: 17 | explicit QConsole(QObject *parent = nullptr); 18 | ~QConsole() override; 19 | 20 | bool isSequential() const override; 21 | bool open(); 22 | void close() override; 23 | qint64 bytesAvailable() const override; 24 | 25 | static QFile *qStdOut(QObject *parent = nullptr); 26 | static QFile *qStdErr(QObject *parent = nullptr); 27 | static QFile *qStdIn(QObject *parent = nullptr); 28 | 29 | protected: 30 | qint64 readData(char *data, qint64 maxlen) override; 31 | qint64 writeData(const char *data, qint64 len) override; 32 | 33 | #ifdef Q_OS_UNIX 34 | private Q_SLOTS: 35 | void activated(); 36 | #endif 37 | 38 | private: 39 | #ifdef Q_OS_WIN 40 | ReadThread *_readThread; 41 | #else 42 | QSocketNotifier *_notifier; 43 | QFile *_in; 44 | #endif 45 | 46 | bool open(OpenMode openMode) override; 47 | 48 | #ifdef Q_OS_UNIX 49 | qint64 nBytes() const; 50 | #endif 51 | }; 52 | 53 | #endif // QCONSOLE_H 54 | -------------------------------------------------------------------------------- /qconsole.pri: -------------------------------------------------------------------------------- 1 | 2 | HEADERS += \ 3 | $$PWD/qconsole.h 4 | 5 | SOURCES += \ 6 | $$PWD/qconsole.cpp 7 | 8 | win32 { 9 | HEADERS += $$PWD/readthread_win.h 10 | SOURCES += $$PWD/readthread_win.cpp 11 | } 12 | 13 | INCLUDEPATH += $$PWD 14 | 15 | QDEP_PACKAGE_EXPORTS += Q_CONSOLE_EXPORT 16 | !qdep_build: DEFINES += "Q_CONSOLE_EXPORT=" 17 | -------------------------------------------------------------------------------- /readthread_win.cpp: -------------------------------------------------------------------------------- 1 | #include "readthread_win.h" 2 | #include 3 | #include 4 | 5 | ReadThread::ReadThread(QObject *parent) : 6 | QThread{parent}, 7 | _buffer{new QBuffer{this}} 8 | { 9 | setTerminationEnabled(true); 10 | } 11 | 12 | QBuffer *ReadThread::buffer() const 13 | { 14 | return _buffer; 15 | } 16 | 17 | void ReadThread::start() 18 | { 19 | _buffer->open(QIODevice::ReadOnly); 20 | QThread::start(); 21 | } 22 | 23 | void ReadThread::stop() 24 | { 25 | requestInterruption(); 26 | if(!wait(450)) { 27 | terminate(); 28 | wait(50); 29 | } 30 | _buffer->close(); 31 | } 32 | 33 | void ReadThread::run() 34 | { 35 | QFile in; 36 | if(!in.open(stdin, QIODevice::ReadOnly | QIODevice::Unbuffered)) { 37 | qCritical() << "Unable to open stdin for reading with error" << in.errorString(); 38 | return; 39 | } 40 | 41 | auto eof = false; 42 | while(!eof && !isInterruptionRequested()) { 43 | if(in.error() != QFile::NoError) 44 | break; 45 | 46 | auto data = in.readLine(); 47 | if(in.atEnd()) 48 | eof = true; 49 | else if(data.isEmpty()) 50 | QThread::msleep(100); 51 | else { 52 | QMetaObject::invokeMethod(this, "addData", Qt::QueuedConnection, 53 | Q_ARG(QByteArray, data)); 54 | } 55 | } 56 | 57 | in.close(); 58 | if(!eof && !isInterruptionRequested()) 59 | qCritical() << "stdin closed unexpectedly!"; 60 | if(eof) 61 | emit eofTriggered(); 62 | } 63 | 64 | void ReadThread::addData(const QByteArray &data) 65 | { 66 | auto &buf = _buffer->buffer(); 67 | buf = buf.mid(static_cast(_buffer->pos())) + data; 68 | _buffer->seek(0); 69 | if(buf.size() == data.size()) 70 | emit newData(); 71 | } 72 | -------------------------------------------------------------------------------- /readthread_win.h: -------------------------------------------------------------------------------- 1 | #ifndef READTHREAD_WIN_H 2 | #define READTHREAD_WIN_H 3 | 4 | #include 5 | #include 6 | 7 | class ReadThread : public QThread 8 | { 9 | Q_OBJECT 10 | 11 | public: 12 | ReadThread(QObject *parent = nullptr); 13 | 14 | QBuffer *buffer() const; 15 | void start(); 16 | void stop(); 17 | 18 | Q_SIGNALS: 19 | void newData(); 20 | void eofTriggered(); 21 | 22 | protected: 23 | void run() override; 24 | 25 | private Q_SLOTS: 26 | void addData(const QByteArray &data); 27 | 28 | private: 29 | QBuffer *_buffer; 30 | }; 31 | 32 | #endif // READTHREAD_WIN_H 33 | --------------------------------------------------------------------------------