Add a Win32Console class that provides a simpler, Qt/C++ interface to the

Win32 Console API.
This commit is contained in:
Ryan Prichard 2011-11-17 03:00:02 -08:00
parent c74f82abdb
commit f77557aa95
5 changed files with 248 additions and 87 deletions

View File

@ -1,74 +1,28 @@
#define _WIN32_WINNT 0x0501
#include "Agent.h"
#include "Win32Console.h"
#include "../Shared/DebugClient.h"
#include "../Shared/AgentMsg.h"
#include <QCoreApplication>
#include <QLocalSocket>
#include <QtDebug>
#include <QTimer>
#include <QSize>
#include <QRect>
#include <string.h>
#include <windows.h>
const int BUFFER_LINE_COUNT = 5000;
static void resizeWindow(HANDLE conout, unsigned short cols, unsigned short rows)
{
// Windows has one API for resizing the screen buffer and a different one
// for resizing the window. It seems that either API can fail if the
// window does not fit on the screen buffer.
CONSOLE_SCREEN_BUFFER_INFO originalInfo;
GetConsoleScreenBufferInfo(conout, &originalInfo);
COORD finalBufferSize = { cols, BUFFER_LINE_COUNT };
SMALL_RECT finalWindowRect = {
0,
BUFFER_LINE_COUNT - rows,
cols - 1,
BUFFER_LINE_COUNT - 1,
};
if (originalInfo.dwSize.Y != BUFFER_LINE_COUNT) {
// TODO: Is it really safe to resize the window down to 1x1?
// TODO: Is there a better way to do this?
SMALL_RECT smallestWindowRect = { 0, 0, 0, 0 };
SetConsoleWindowInfo(conout, TRUE, &smallestWindowRect);
SetConsoleScreenBufferSize(conout, finalBufferSize);
SetConsoleWindowInfo(conout, TRUE, &finalWindowRect);
} else {
if (cols > originalInfo.dwSize.X) {
SetConsoleScreenBufferSize(conout, finalBufferSize);
SetConsoleWindowInfo(conout, TRUE, &finalWindowRect);
} else {
SetConsoleWindowInfo(conout, TRUE, &finalWindowRect);
SetConsoleScreenBufferSize(conout, finalBufferSize);
}
}
// Don't move the cursor, even if the cursor is now off the screen.
}
Agent::Agent(const QString &socketServer,
int initialCols,
int initialRows,
QObject *parent) :
QObject(parent), m_timer(NULL)
{
m_conin = CreateFile(
L"CONIN$",
GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL, OPEN_EXISTING, 0, NULL);
m_conout = CreateFile(
L"CONOUT$",
GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL, OPEN_EXISTING, 0, NULL);
Q_ASSERT(m_conin != NULL);
Q_ASSERT(m_conout != NULL);
resizeWindow(m_conout, initialCols, initialRows);
COORD initialCursorPos = { 0, BUFFER_LINE_COUNT - initialRows };
SetConsoleCursorPosition(m_conout, initialCursorPos);
m_console = new Win32Console(this);
resizeWindow(initialCols, initialRows);
m_console->setCursorPosition(QPoint(0, BUFFER_LINE_COUNT - initialRows));
// Connect to the named pipe.
m_socket = new QLocalSocket(this);
@ -90,9 +44,7 @@ Agent::Agent(const QString &socketServer,
Agent::~Agent()
{
HWND hwnd = GetConsoleWindow();
if (hwnd != NULL)
PostMessage(hwnd, WM_CLOSE, 0, 0);
m_console->postCloseMessage();
}
void Agent::socketReadyRead()
@ -104,8 +56,7 @@ void Agent::socketReadyRead()
m_socket->read((char*)&msg, sizeof(msg));
switch (msg.type) {
case AgentMsg::InputRecord: {
DWORD dummy;
WriteConsoleInput(m_conin, &msg.u.inputRecord, 1, &dummy);
m_console->writeInput(&msg.u.inputRecord);
break;
}
case AgentMsg::WindowSize: {
@ -120,7 +71,7 @@ void Agent::socketReadyRead()
continue;
}
Trace("resize started");
resizeWindow(m_conout, msg.u.windowSize.cols, msg.u.windowSize.rows);
resizeWindow(msg.u.windowSize.cols, msg.u.windowSize.rows);
Trace("resize done");
break;
}
@ -156,40 +107,28 @@ void Agent::pollTimeout()
}
}
void Agent::resizeWindow(int cols, int rows)
{
m_console->reposition(
QSize(cols, BUFFER_LINE_COUNT),
QRect(0, BUFFER_LINE_COUNT - rows, cols, rows));
}
void Agent::scrapeOutput()
{
CONSOLE_SCREEN_BUFFER_INFO info;
if (!GetConsoleScreenBufferInfo(m_conout, &info)) {
Trace("GetConsoleScreenBufferInfo failed");
return;
}
COORD size;
size.X = info.srWindow.Right - info.srWindow.Left + 1;
size.Y = info.srWindow.Bottom - info.srWindow.Top + 1;
COORD zeroPos = {0, 0};
SMALL_RECT readWindow = info.srWindow;
const QRect windowRect = m_console->windowRect();
CHAR_INFO readBuffer[64 * 1024 / sizeof(CHAR_INFO)]; // TODO: buf overflow
if (!ReadConsoleOutput(m_conout, readBuffer, size, zeroPos, &readWindow)) {
Trace("ReadConsoleOutput failed");
return;
}
char writeBuffer[sizeof(readBuffer) / sizeof(CHAR_INFO)]; // TODO: buf overflow
char *pwrite = writeBuffer;
if (memcmp(&info.srWindow, &readWindow, sizeof(SMALL_RECT))) {
Trace("ReadConsoleOutput returned a different-sized buffer");
return;
}
m_console->read(windowRect, readBuffer);
// Simplest algorithm -- just send the whole screen.
for (int y = 0; y < size.Y; ++y) {
for (int x = 0; x < size.X; ++x) {
CHAR_INFO *pch = &readBuffer[y * size.X + x];
for (int y = 0; y < windowRect.height(); ++y) {
for (int x = 0; x < windowRect.width(); ++x) {
CHAR_INFO *pch = &readBuffer[y * windowRect.width() + x];
*pwrite++ = pch->Char.AsciiChar;
}
if (y < size.Y - 1) {
if (y < windowRect.height() - 1) {
*pwrite++ = '\r';
*pwrite++ = '\n';
}

View File

@ -4,6 +4,7 @@
#include <QObject>
#include <windows.h>
class Win32Console;
class QLocalSocket;
class QTimer;
@ -24,13 +25,13 @@ private slots:
void pollTimeout();
private:
void resizeWindow(int cols, int rows);
void scrapeOutput();
private:
Win32Console *m_console;
QLocalSocket *m_socket;
QTimer *m_timer;
HANDLE m_conin;
HANDLE m_conout;
};
#endif // AGENT_H

View File

@ -19,8 +19,12 @@ TEMPLATE = app
SOURCES += main.cc \
Agent.cc \
../Shared/DebugClient.cc
../Shared/DebugClient.cc \
Win32Console.cc
HEADERS += \
Agent.h \
../Shared/DebugClient.h
../Shared/DebugClient.h \
Win32Console.h

168
Agent/Win32Console.cc Normal file
View File

@ -0,0 +1,168 @@
#define _WIN32_WINNT 0x0501
#include "Win32Console.h"
#include <QSize>
#include <QRect>
#include <windows.h>
static inline SMALL_RECT smallRectFromQRect(const QRect &rect)
{
SMALL_RECT smallRect = { rect.left(),
rect.top(),
rect.left() + rect.width() - 1,
rect.top() + rect.height() - 1 };
return smallRect;
}
Win32Console::Win32Console(QObject *parent) :
QObject(parent)
{
m_conin = CreateFile(
L"CONIN$",
GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL, OPEN_EXISTING, 0, NULL);
m_conout = CreateFile(
L"CONOUT$",
GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL, OPEN_EXISTING, 0, NULL);
Q_ASSERT(m_conin != NULL);
Q_ASSERT(m_conout != NULL);
}
Win32Console::~Win32Console()
{
CloseHandle(m_conin);
CloseHandle(m_conout);
}
HANDLE Win32Console::conin()
{
return m_conin;
}
HANDLE Win32Console::conout()
{
return m_conout;
}
HWND Win32Console::hwnd()
{
return GetConsoleWindow();
}
void Win32Console::postCloseMessage()
{
HWND h = hwnd();
if (h != NULL)
PostMessage(h, WM_CLOSE, 0, 0);
}
QSize Win32Console::bufferSize()
{
CONSOLE_SCREEN_BUFFER_INFO info;
GetConsoleScreenBufferInfo(m_conout, &info);
// TODO: error handling
return QSize(info.dwSize.X, info.dwSize.Y);
}
QRect Win32Console::windowRect()
{
CONSOLE_SCREEN_BUFFER_INFO info;
GetConsoleScreenBufferInfo(m_conout, &info);
// TODO: error handling
return QRect(info.srWindow.Left,
info.srWindow.Top,
info.srWindow.Right - info.srWindow.Left + 1,
info.srWindow.Bottom - info.srWindow.Top + 1);
}
void Win32Console::resizeBuffer(const QSize &size)
{
COORD bufferSize = { size.width(), size.height() };
SetConsoleScreenBufferSize(m_conout, bufferSize);
// TODO: error handling
}
void Win32Console::moveWindow(const QRect &rect)
{
SMALL_RECT windowRect = smallRectFromQRect(rect);
SetConsoleWindowInfo(m_conout, TRUE, &windowRect);
// TODO: error handling
}
void Win32Console::reposition(const QSize &newBufferSize, const QRect &newWindowRect)
{
// Windows has one API for resizing the screen buffer and a different one
// for resizing the window. It seems that either API can fail if the
// window does not fit on the screen buffer.
const QRect origWindowRect(windowRect());
const QRect origBufferRect(QPoint(), bufferSize());
Q_ASSERT(!newBufferSize.isEmpty());
QRect bufferRect(QPoint(), newBufferSize);
Q_ASSERT(bufferRect.contains(newWindowRect));
QRect tempWindowRect = origWindowRect.intersected(bufferRect);
if (tempWindowRect.width() <= 0) {
tempWindowRect.setLeft(newBufferSize.width() - 1);
tempWindowRect.setWidth(1);
}
if (tempWindowRect.height() <= 0) {
tempWindowRect.setTop(newBufferSize.height() - 1);
tempWindowRect.setHeight(1);
}
// Alternatively, if we can immediately use the new window size,
// do that instead.
if (origBufferRect.contains(newWindowRect))
tempWindowRect = newWindowRect;
if (tempWindowRect != origWindowRect)
moveWindow(tempWindowRect);
resizeBuffer(newBufferSize);
if (newWindowRect != tempWindowRect)
moveWindow(newWindowRect);
}
QPoint Win32Console::cursorPosition()
{
CONSOLE_SCREEN_BUFFER_INFO info;
GetConsoleScreenBufferInfo(m_conout, &info);
// TODO: error handling
return QPoint(info.dwCursorPosition.X, info.dwCursorPosition.Y);
}
void Win32Console::setCursorPosition(const QPoint &point)
{
COORD coord = { point.x(), point.y() };
SetConsoleCursorPosition(m_conout, coord);
// TODO: error handling
}
void Win32Console::writeInput(const INPUT_RECORD *ir, int count)
{
DWORD dummy = 0;
WriteConsoleInput(m_conin, ir, count, &dummy);
// TODO: error handling
}
void Win32Console::read(const QRect &rect, CHAR_INFO *data)
{
COORD bufferSize = { rect.width(), rect.height() };
COORD zeroCoord = { 0, 0 };
SMALL_RECT smallRect = smallRectFromQRect(rect);
ReadConsoleOutput(m_conout, data, bufferSize, zeroCoord, &smallRect);
// TODO: error handling
}
void Win32Console::write(const QRect &rect, const CHAR_INFO *data)
{
COORD bufferSize = { rect.width(), rect.height() };
COORD zeroCoord = { 0, 0 };
SMALL_RECT smallRect = smallRectFromQRect(rect);
WriteConsoleOutput(m_conout, data, bufferSize, zeroCoord, &smallRect);
// TODO: error handling
}

49
Agent/Win32Console.h Normal file
View File

@ -0,0 +1,49 @@
#ifndef WIN32CONSOLE_H
#define WIN32CONSOLE_H
#include <QObject>
#include <QSize>
#include <QRect>
#include <QPoint>
#include <windows.h>
class Win32Console : public QObject
{
Q_OBJECT
public:
explicit Win32Console(QObject *parent = 0);
virtual ~Win32Console();
HANDLE conin();
HANDLE conout();
HWND hwnd();
void postCloseMessage();
// Buffer and window sizes.
QSize bufferSize();
QRect windowRect();
void resizeBuffer(const QSize &size);
void moveWindow(const QRect &rect);
void reposition(const QSize &bufferSize, const QRect &windowRect);
// Cursor.
QPoint cursorPosition();
void setCursorPosition(const QPoint &point);
// Input stream.
void writeInput(const INPUT_RECORD *ir, int count=1);
// Screen content.
void read(const QRect &rect, CHAR_INFO *data);
void write(const QRect &rect, const CHAR_INFO *data);
signals:
public slots:
private:
HANDLE m_conin;
HANDLE m_conout;
};
#endif // WIN32CONSOLE_H