reading multi-line input from QSocketNotifier
-
I want to write a GUI application that can also be steered from the command line. The following self-contained program reveals a first problem.
#include <QAction> #include <QApplication> #include <QLayout> #include <QMainWindow> #include <QSocketNotifier> #include <QTextStream> #include <QToolButton> class MainWin : public QMainWindow { public: MainWin(); void onCommand(const QString& cmd); }; MainWin::MainWin() { setMinimumWidth(360); auto* a2 = new QAction{"quit"}; connect(a2, &QAction::triggered, [this]() -> void { close(); }); auto* b2 = new QToolButton; b2->setDefaultAction(a2); b2->setToolButtonStyle(Qt::ToolButtonTextOnly); auto* center = new QWidget; setCentralWidget(center); auto* vbox = new QVBoxLayout(center); vbox->addWidget(b2); show(); } void MainWin::onCommand(const QString& cmd) { qDebug() << "READ " << cmd; // this shall be extended so that the GUI can also be steered from the command line } int main(int argc, char **argv) { QApplication app(argc, argv); app.setApplicationName("modal dialog test"); MainWin mw; auto* notifier = new QSocketNotifier{fileno(stdin), QSocketNotifier::Read}; QObject::connect( notifier, &QSocketNotifier::activated, [&mw](QSocketDescriptor, QSocketNotifier::Type type) { if (type == QSocketNotifier::Read) { QTextStream qtin{stdin}; // .... Variant A .... mw.onCommand(qtin.readLine()); // .... Variant B .... do { mw.onCommand(qtin.readLine()); } while (!qtin.atEnd()); // .... End of variants } }); return app.exec(); }
Variant A works fine - as long as I enter the input manually, line by line. However, it fails if I paste a multi-line text block to stdin, or pipe a file to stdin.
Variant B correctly reads copied & pasted multi-line input - but then it blocks the application forever; the "quit" button in the GUI no longer reacts to clicks.
How to reconcile these two variants to meet all requirements?
-
// Variant C auto* notifier = new QSocketNotifier{fileno(stdin), QSocketNotifier::Read}; QObject::connect( notifier, &QSocketNotifier::activated, [&mw](QSocketDescriptor, QSocketNotifier::Type type) { qDebug() << "notified"; if (type == QSocketNotifier::Read) { QTextStream qtin{stdin}; QElapsedTimer timer; timer.start(); do { qDebug() << "reading"; QString cmd = qtin.readLine(); qDebug() << "read " << cmd; if (cmd.isEmpty()) break; mw.onCommand(cmd); } while (timer.nsecsElapsed() < 250e6); } });
works for copied & pasted multiline-input, but not for piped input
a.out < textfile
, which results in an endless loop. -
-
@Joachim-W
I looked at your progress here but have not had an opportunity to examine it further. But here are a couple of points which strike me:-
readLine()
should read one line from input. It should not matter whether that comes from terminal (with or without pasting) or redirection. The fact that you seem to say sometimes it reads/returns one line and sometimes multiple lines should not happen. A line is a line. TheQTextStream
may (or may not) buffer further lines which are available (e.g. from redirection) butreadLine()
should only return a single line. -
When reading from redirection you can/should be able to read till "end of file", indicating the end of input. But when reading from terminal there is no "end", user could type more at any time, so no "end of file".
-
You should not need a timer. One
QSocketNotifier
notification should be received when there is one or more lines/input available. Process all lines which are available (however you test for that) then exit. That should be it for the redirection case. For the terminal case at some future point a newQSocketNotifier
notification should arrive at which point you re-enter and process whatever is available anew. You will need areadLine()
call which returns with "empty" when no more input is currently available, or a test which says whether there is any further input available at this point before calling a blockingreadLine()
. It may be better to callreadAll()
: that should return all data which is available at that point, I believe, but will not block likereadLine()
when no further data is there. This will work for redirection too.
-