aboutsummaryrefslogtreecommitdiffstats
path: root/src/app/qbs/stdinreader.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/app/qbs/stdinreader.cpp')
-rw-r--r--src/app/qbs/stdinreader.cpp194
1 files changed, 166 insertions, 28 deletions
diff --git a/src/app/qbs/stdinreader.cpp b/src/app/qbs/stdinreader.cpp
index 5f00d7de4..4708ff53c 100644
--- a/src/app/qbs/stdinreader.cpp
+++ b/src/app/qbs/stdinreader.cpp
@@ -43,6 +43,7 @@
#include <QtCore/qfile.h>
#include <QtCore/qsocketnotifier.h>
+#include <QtCore/qthread.h>
#include <QtCore/qtimer.h>
#include <cerrno>
@@ -111,46 +112,183 @@ public:
WindowsStdinReader(QObject *parent) : StdinReader(parent) {}
private:
- void start() override
- {
#ifdef Q_OS_WIN32
- m_stdinHandle = GetStdHandle(STD_INPUT_HANDLE);
- if (!m_stdinHandle) {
- emit errorOccurred(tr("Failed to create handle for standard input."));
- return;
+ class FileReaderThread : public QThread
+ {
+ public:
+ FileReaderThread(WindowsStdinReader &parent, HANDLE stdInHandle, HANDLE exitEventHandle)
+ : QThread(&parent), m_stdIn{stdInHandle}, m_exitEvent{exitEventHandle} { }
+ ~FileReaderThread()
+ {
+ wait();
+ CloseHandle(m_exitEvent);
+ }
+
+ void run() override
+ {
+ WindowsStdinReader *r = static_cast<WindowsStdinReader *>(parent());
+
+ char buf[1024];
+ while (true) {
+ DWORD bytesRead = 0;
+ if (!ReadFile(m_stdIn, buf, sizeof buf, &bytesRead, nullptr)) {
+ emit r->errorOccurred(tr("Failed to read from input channel."));
+ break;
+ }
+ if (!bytesRead)
+ break;
+ emit r->dataAvailable(QByteArray(buf, bytesRead));
+ }
+ }
+ private:
+ HANDLE m_stdIn;
+ HANDLE m_exitEvent;
+ };
+
+ class ConsoleReaderThread : public QThread
+ {
+ public:
+ ConsoleReaderThread(WindowsStdinReader &parent, HANDLE stdInHandle, HANDLE exitEventHandle)
+ : QThread(&parent), m_stdIn{stdInHandle}, m_exitEvent{exitEventHandle} { }
+ virtual ~ConsoleReaderThread() override
+ {
+ SetEvent(m_exitEvent);
+ wait();
+ CloseHandle(m_exitEvent);
}
- // A timer seems slightly less awful than to block in a thread
- // (how would we abort that one?), but ideally we'd like
- // to have a signal-based approach like in the Unix variant.
- const auto timer = new QTimer(this);
- connect(timer, &QTimer::timeout, this, [this, timer] {
+ void run() override
+ {
+ WindowsStdinReader *r = static_cast<WindowsStdinReader *>(parent());
+
+ DWORD origConsoleMode;
+ GetConsoleMode(m_stdIn, &origConsoleMode);
+ DWORD consoleMode = ENABLE_PROCESSED_INPUT;
+ SetConsoleMode(m_stdIn, consoleMode);
+
+ HANDLE handles[2] = {m_exitEvent, m_stdIn};
char buf[1024];
- DWORD bytesAvail;
- if (!PeekNamedPipe(m_stdinHandle, nullptr, 0, nullptr, &bytesAvail, nullptr)) {
- timer->stop();
- emit errorOccurred(tr("Failed to read from input channel."));
+ while (true) {
+ auto result = WaitForMultipleObjects(2, handles, FALSE, INFINITE);
+ if (result == WAIT_OBJECT_0)
+ break;
+ INPUT_RECORD consoleInput;
+ DWORD inputsRead = 0;
+ if (!PeekConsoleInputA(m_stdIn, &consoleInput, 1, &inputsRead)) {
+ emit r->errorOccurred(tr("Failed to read from input channel."));
+ break;
+ }
+ if (inputsRead) {
+ if (consoleInput.EventType != KEY_EVENT
+ || !consoleInput.Event.KeyEvent.bKeyDown
+ || !consoleInput.Event.KeyEvent.uChar.AsciiChar) {
+ if (!ReadConsoleInputA(m_stdIn, &consoleInput, 1, &inputsRead)) {
+ emit r->errorOccurred(tr("Failed to read console input."));
+ break;
+ }
+ } else {
+ DWORD bytesRead = 0;
+ if (!ReadConsoleA(m_stdIn, buf, sizeof buf, &bytesRead, nullptr)) {
+ emit r->errorOccurred(tr("Failed to read console."));
+ break;
+ }
+ emit r->dataAvailable(QByteArray(buf, bytesRead));
+ }
+ }
+ }
+ SetConsoleMode(m_stdIn, origConsoleMode);
+ }
+ private:
+ HANDLE m_stdIn;
+ HANDLE m_exitEvent;
+ };
+
+ class PipeReaderThread : public QThread
+ {
+ public:
+ PipeReaderThread(WindowsStdinReader &parent, HANDLE stdInHandle, HANDLE exitEventHandle)
+ : QThread(&parent), m_stdIn{stdInHandle}, m_exitEvent{exitEventHandle} { }
+ virtual ~PipeReaderThread() override
+ {
+ SetEvent(m_exitEvent);
+ wait();
+ CloseHandle(m_exitEvent);
+ }
+
+ void run() override
+ {
+ WindowsStdinReader *r = static_cast<WindowsStdinReader *>(parent());
+
+ OVERLAPPED overlapped = {};
+ overlapped.hEvent = CreateEventA(NULL, TRUE, TRUE, NULL);
+ if (!overlapped.hEvent) {
+ emit r->errorOccurred(StdinReader::tr("Failed to create handle for overlapped event."));
return;
}
- while (bytesAvail > 0) {
- DWORD bytesRead;
- if (!ReadFile(m_stdinHandle, buf, std::min<DWORD>(bytesAvail, sizeof buf),
- &bytesRead, nullptr)) {
- timer->stop();
- emit errorOccurred(tr("Failed to read from input channel."));
- return;
+
+ char buf[1024];
+ DWORD bytesRead;
+ HANDLE handles[2] = {m_exitEvent, overlapped.hEvent};
+ while (true) {
+ bytesRead = 0;
+ auto readResult = ReadFile(m_stdIn, buf, sizeof buf, NULL, &overlapped);
+ if (!readResult) {
+ if (GetLastError() != ERROR_IO_PENDING) {
+ emit r->errorOccurred(StdinReader::tr("ReadFile Failed."));
+ break;
+ }
+
+ auto result = WaitForMultipleObjects(2, handles, FALSE, INFINITE);
+ if (result == WAIT_OBJECT_0)
+ break;
+ }
+ if (!GetOverlappedResult(m_stdIn, &overlapped, &bytesRead, FALSE)) {
+ if (GetLastError() != ERROR_HANDLE_EOF)
+ emit r->errorOccurred(StdinReader::tr("Error GetOverlappedResult."));
+ break;
}
- emit dataAvailable(QByteArray(buf, bytesRead));
- bytesAvail -= bytesRead;
+ emit r->dataAvailable(QByteArray(buf, bytesRead));
}
- });
- timer->start(10);
+ CancelIo(m_stdIn);
+ CloseHandle(overlapped.hEvent);
+ }
+ private:
+ HANDLE m_stdIn;
+ HANDLE m_exitEvent;
+ };
#endif
- }
+ void start() override
+ {
#ifdef Q_OS_WIN32
- HANDLE m_stdinHandle;
+ HANDLE stdInHandle = GetStdHandle(STD_INPUT_HANDLE);
+ if (!stdInHandle) {
+ emit errorOccurred(StdinReader::tr("Failed to create handle for standard input."));
+ return;
+ }
+ HANDLE exitEventHandle = CreateEventA(NULL, TRUE, FALSE, NULL);
+ if (!exitEventHandle) {
+ emit errorOccurred(StdinReader::tr("Failed to create handle for exit event."));
+ return;
+ }
+
+ auto result = GetFileType(stdInHandle);
+ switch (result) {
+ case FILE_TYPE_CHAR:
+ (new ConsoleReaderThread(*this, stdInHandle, exitEventHandle))->start();
+ return;
+ case FILE_TYPE_PIPE:
+ (new PipeReaderThread(*this, stdInHandle, exitEventHandle))->start();
+ return;
+ case FILE_TYPE_DISK:
+ (new FileReaderThread(*this, stdInHandle, exitEventHandle))->start();
+ return;
+ default:
+ emit errorOccurred(StdinReader::tr("Unable to handle unknown input type"));
+ return;
+ }
#endif
+ }
};
StdinReader *StdinReader::create(QObject *parent)