mirror of
https://github.com/epasveer/seer.git
synced 2026-08-30 00:50:42 +08:00
579 lines
14 KiB
C++
579 lines
14 KiB
C++
// SPDX-FileCopyrightText: 2021 Ernie Pasveer <epasveer@att.net>
|
|
//
|
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
|
|
#include "SeerConsoleWidget.h"
|
|
#include <QtWidgets/QPlainTextEdit>
|
|
#include <QtWidgets/QCheckBox>
|
|
#include <QtWidgets/QFileDialog>
|
|
#include <QtWidgets/QFontDialog>
|
|
#include <QtWidgets/QMessageBox>
|
|
#include <QtWidgets/QScrollBar>
|
|
#include <QtPrintSupport/QPrinter>
|
|
#include <QtPrintSupport/QPrintDialog>
|
|
#include <QtGui/QFont>
|
|
#include <QtGui/QIcon>
|
|
#include <QtCore/QTextStream>
|
|
#include <QtCore/QSettings>
|
|
#include <QtCore/QDebug>
|
|
#include <unistd.h>
|
|
#include <stdlib.h>
|
|
#include <fcntl.h>
|
|
#include <termios.h>
|
|
#include <sys/ioctl.h>
|
|
#include <unistd.h>
|
|
|
|
SeerConsoleWidget::SeerConsoleWidget (QWidget* parent) : QWidget(parent) {
|
|
|
|
// Init variables.
|
|
_terminalDeviceName = "";
|
|
_ttyFD = -1;
|
|
_ttyListener = 0;
|
|
_enableStdout = false;
|
|
_enableWrap = false;
|
|
|
|
// Set up UI.
|
|
setupUi(this);
|
|
|
|
// Setup the widgets
|
|
setWindowTitle("Seer Console");
|
|
|
|
textEdit->setReadOnly(true);
|
|
textEdit->setTextInteractionFlags(textEdit->textInteractionFlags() | Qt::TextSelectableByKeyboard); // Show cursor
|
|
textEdit->setLineWrapMode(QPlainTextEdit::NoWrap); // No wrap
|
|
|
|
// Default font.
|
|
QFont font;
|
|
font.setFamily("monospace");
|
|
font.setStyleHint(QFont::Monospace);
|
|
font.setFixedPitch(true);
|
|
|
|
// Set the widget's font.
|
|
QTextCharFormat format = textEdit->currentCharFormat();
|
|
format.setFont(font);
|
|
textEdit->setCurrentCharFormat(format);
|
|
textEdit->setFont(font);
|
|
|
|
wrapTextCheckBox->setCheckState(Qt::Unchecked); // No wrap
|
|
|
|
// Connect things.
|
|
QObject::connect(clearButton, &QPushButton::clicked, this, &SeerConsoleWidget::handleClearButton);
|
|
QObject::connect(printButton, &QPushButton::clicked, this, &SeerConsoleWidget::handlePrintButton);
|
|
QObject::connect(saveButton, &QPushButton::clicked, this, &SeerConsoleWidget::handleSaveButton);
|
|
QObject::connect(fontButton, &QPushButton::clicked, this, &SeerConsoleWidget::handleFontButton);
|
|
QObject::connect(wrapTextCheckBox, &QCheckBox::clicked, this, &SeerConsoleWidget::handleWrapTextCheckBox);
|
|
QObject::connect(stdoutCheckBox, &QCheckBox::clicked, this, &SeerConsoleWidget::handleStdoutCheckBox);
|
|
QObject::connect(stdinLineEdit, &QLineEdit::returnPressed, this, &SeerConsoleWidget::handleStdinLineEdit);
|
|
|
|
// Restore window settings.
|
|
readSettings();
|
|
}
|
|
|
|
SeerConsoleWidget::~SeerConsoleWidget () {
|
|
deleteTerminal(); // Will disconnect terminal first befor deleting.
|
|
}
|
|
|
|
void SeerConsoleWidget::handleText (const char* buffer, int count) {
|
|
|
|
if (count < 1) {
|
|
return;
|
|
}
|
|
|
|
// Write text to stdout on terminal that started Seer..
|
|
// Ignore errors.
|
|
if (isStdoutEnabled()) {
|
|
ssize_t n = ::write (STDOUT_FILENO, buffer, count);
|
|
if (n < count) {
|
|
qDebug("Can't write stdout to terminal");
|
|
}
|
|
}
|
|
|
|
// Write text to Ansi widget.
|
|
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
|
QString str = _utf8Decoder.decode(QByteArrayView(buffer, count));
|
|
#else
|
|
QString str = QString::fromUtf8(buffer, count);
|
|
#endif
|
|
|
|
textEdit->insertAnsiText(str);
|
|
textEdit->ensureCursorVisible();
|
|
|
|
// Send signal of new text if this widget is not visable.
|
|
if (isVisible() == false) {
|
|
emit newTextAdded();
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
void SeerConsoleWidget::handleChangeWindowTitle (QString title) {
|
|
|
|
if (title == "") {
|
|
setWindowTitle("Seer Console");
|
|
}else{
|
|
setWindowTitle("Seer Console - '" + title + "'");
|
|
}
|
|
}
|
|
|
|
void SeerConsoleWidget::handleTabDetached (QWidget* widget) {
|
|
|
|
if (widget != (QWidget*)this) {
|
|
return;
|
|
}
|
|
|
|
// Resize the detached console with the size from
|
|
// the settings.
|
|
QSettings settings;
|
|
|
|
settings.beginGroup("consolewindow"); {
|
|
resize(settings.value("size", QSize(800, 600)).toSize());
|
|
} settings.endGroup();
|
|
}
|
|
|
|
void SeerConsoleWidget::handleTabReattached (QWidget* widget) {
|
|
// Do nothing for now.
|
|
Q_UNUSED(widget);
|
|
}
|
|
|
|
void SeerConsoleWidget::handleClearButton () {
|
|
textEdit->clear();
|
|
}
|
|
|
|
void SeerConsoleWidget::handlePrintButton () {
|
|
|
|
QPrinter printer;
|
|
|
|
QPrintDialog* dlg = new QPrintDialog(&printer, this);
|
|
|
|
if (dlg->exec() != QDialog::Accepted) {
|
|
return;
|
|
}
|
|
|
|
QTextDocument* document = textEdit->document();
|
|
|
|
document->print(&printer);
|
|
}
|
|
|
|
void SeerConsoleWidget::handleSaveButton () {
|
|
|
|
QFileDialog dialog(this, "Seer log file", "./", "Logs (*.log);;Text files (*.txt);;All files (*.*)");
|
|
dialog.setOptions(QFileDialog::DontUseNativeDialog);
|
|
dialog.setAcceptMode(QFileDialog::AcceptSave);
|
|
dialog.setFileMode(QFileDialog::AnyFile);
|
|
dialog.setDefaultSuffix("log");
|
|
dialog.selectFile("gdboutput.log");
|
|
|
|
if (dialog.exec() != QDialog::Accepted) {
|
|
return;
|
|
}
|
|
|
|
QStringList files = dialog.selectedFiles();
|
|
|
|
if (files.size() == 0) {
|
|
return;
|
|
}
|
|
|
|
if (files.size() > 1) {
|
|
QMessageBox::critical(this, tr("Error"), tr("Select only 1 file."));
|
|
return;
|
|
}
|
|
|
|
QFile file(files[0]);
|
|
|
|
if (file.open(QIODevice::ReadWrite)) {
|
|
QTextStream stream(&file);
|
|
stream << textEdit->toPlainText();
|
|
file.flush();
|
|
file.close();
|
|
|
|
}else{
|
|
QMessageBox::critical(this, tr("Error"), tr("Cannot save log to file."));
|
|
return;
|
|
}
|
|
}
|
|
|
|
void SeerConsoleWidget::handleFontButton () {
|
|
|
|
// Get current format for the font name.
|
|
bool ok;
|
|
|
|
QFont font = QFontDialog::getFont(&ok, textEdit->font().toString(), this, "Seer - Select Console Font", QFontDialog::DontUseNativeDialog|QFontDialog::MonospacedFonts);
|
|
|
|
if (ok == false) {
|
|
return;
|
|
}
|
|
|
|
// Set the widget's font.
|
|
QTextCharFormat format = textEdit->currentCharFormat();
|
|
format.setFont(font);
|
|
textEdit->setCurrentCharFormat(format);
|
|
textEdit->setFont(font);
|
|
|
|
writeFontSettings();
|
|
}
|
|
|
|
void SeerConsoleWidget::handleWrapTextCheckBox () {
|
|
|
|
if (wrapTextCheckBox->checkState() == Qt::Unchecked) {
|
|
textEdit->setLineWrapMode(QPlainTextEdit::NoWrap); // No wrap
|
|
_enableWrap = false;
|
|
}else{
|
|
textEdit->setLineWrapMode(QPlainTextEdit::WidgetWidth); // Wrap at end of widget
|
|
_enableWrap = true;
|
|
}
|
|
|
|
writeSettings();
|
|
}
|
|
|
|
void SeerConsoleWidget::handleStdoutCheckBox () {
|
|
|
|
// Don't write text to the terminal's stdout.
|
|
if (stdoutCheckBox->checkState() == Qt::Unchecked) {
|
|
enableStdout(false);
|
|
writeSettings();
|
|
return;
|
|
}
|
|
|
|
// If stdout is not valid, don't set it.
|
|
if (fcntl(STDOUT_FILENO, F_GETFD) == -1) {
|
|
|
|
QMessageBox::critical(this, tr("Error"), tr("stdout file descriptor is not valid.\nDisabling writing to stdout."));
|
|
|
|
enableStdout(false);
|
|
writeSettings();
|
|
return;
|
|
}
|
|
|
|
// All good to write to stdout
|
|
enableStdout(true);
|
|
writeSettings();
|
|
}
|
|
|
|
void SeerConsoleWidget::handleStdinLineEdit () {
|
|
|
|
QString str = stdinLineEdit->text();
|
|
|
|
stdinLineEdit->clear();
|
|
|
|
str += '\n';
|
|
|
|
std::string s = str.toStdString();
|
|
|
|
if (isTerminalCreated() == false) {
|
|
return;
|
|
}
|
|
|
|
int n = write(_ttyFD, s.c_str(), s.length());
|
|
|
|
if (n != (signed long int)s.length()) {
|
|
qWarning() << "Not able to process stdin of" << s.length() << "bytes.";
|
|
}
|
|
|
|
fsync(_ttyFD);
|
|
}
|
|
|
|
void SeerConsoleWidget::handleTerminalOutput (int socketfd) {
|
|
|
|
if (isTerminalCreated() == false) {
|
|
return;
|
|
}
|
|
|
|
Q_UNUSED(socketfd);
|
|
|
|
char buffer[1024];
|
|
|
|
while (1) {
|
|
int n = read(_ttyFD, buffer, sizeof(buffer));
|
|
|
|
if (n < 0) {
|
|
if (errno == EAGAIN) {
|
|
break;
|
|
}
|
|
|
|
if (errno == EIO) {
|
|
// Disconnect terminal if tty has an I/O error.
|
|
// Can be reconnected later just before gdb restarts it's target program.
|
|
disconnectTerminal();
|
|
break;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
handleText(buffer, n);
|
|
break;
|
|
}
|
|
}
|
|
|
|
void SeerConsoleWidget::createTerminal () {
|
|
|
|
// Is terminal already created?
|
|
if (isTerminalCreated()) {
|
|
qDebug() << "Terminal already created!";
|
|
return;
|
|
}
|
|
|
|
// Create tty and its permissions.
|
|
_ttyFD = posix_openpt(O_RDWR | O_NOCTTY);
|
|
|
|
if (_ttyFD < 0) {
|
|
qDebug() << "Failed to create tty" << strerror(errno);
|
|
return;
|
|
}
|
|
|
|
if (grantpt(_ttyFD)) {
|
|
qDebug() << "Failed to grant pt" << strerror(errno);
|
|
::close(_ttyFD); _ttyFD = -1;
|
|
return;
|
|
}
|
|
|
|
if (unlockpt(_ttyFD)) {
|
|
qDebug() << "Failed to unlock pt" << strerror(errno);
|
|
::close(_ttyFD); _ttyFD = -1;
|
|
return;
|
|
}
|
|
|
|
// Turn off blocking.
|
|
fcntl(_ttyFD, F_SETFL, O_NDELAY);
|
|
|
|
// Set window size
|
|
struct winsize term_winsize;
|
|
|
|
term_winsize.ws_col = 80;
|
|
term_winsize.ws_row = 20;
|
|
term_winsize.ws_xpixel = 80 * 8;
|
|
term_winsize.ws_ypixel = 20 * 8;
|
|
|
|
if (ioctl(_ttyFD, TIOCSWINSZ, &term_winsize) < 0) {
|
|
qDebug() << "ioctl TIOCSWINSZ failed" << strerror(errno);
|
|
}
|
|
|
|
// Set controlling
|
|
if (ioctl(_ttyFD, TIOCSCTTY, (char *)0) < 0) {
|
|
// Seems to work even though this fails.
|
|
// qDebug() << "ioctl TIOCSCTTY failed" << strerror(errno);
|
|
}
|
|
|
|
// Save the device name.
|
|
_terminalDeviceName = ptsname(_ttyFD);
|
|
|
|
// Set maximum blocks to 0 (unlimited).
|
|
// The createTerminal can be followed with a setScrollLines() call.
|
|
setScrollLines(0);
|
|
}
|
|
|
|
void SeerConsoleWidget::deleteTerminal () {
|
|
|
|
disconnectTerminal();
|
|
|
|
_terminalDeviceName = "";
|
|
|
|
if (isTerminalCreated() == false) {
|
|
return;
|
|
}
|
|
|
|
::close(_ttyFD); _ttyFD = -1;
|
|
}
|
|
|
|
void SeerConsoleWidget::connectTerminal () {
|
|
|
|
if (isTerminalConnected()) {
|
|
//qDebug() << "Terminal is already connected!";
|
|
return;
|
|
}
|
|
|
|
if (isTerminalCreated() == false) {
|
|
qDebug() << "Can't connect Terminal. No _ttyFD!";
|
|
return;
|
|
}
|
|
|
|
_ttyListener = new QSocketNotifier(_ttyFD, QSocketNotifier::Read);
|
|
|
|
QObject::connect(_ttyListener, &QSocketNotifier::activated, this, &SeerConsoleWidget::handleTerminalOutput);
|
|
}
|
|
|
|
void SeerConsoleWidget::disconnectTerminal () {
|
|
|
|
if (isTerminalConnected() == false) {
|
|
//qDebug() << "Terminal is already disconnected!";
|
|
return;
|
|
}
|
|
|
|
QObject::disconnect(_ttyListener, &QSocketNotifier::activated, this, &SeerConsoleWidget::handleTerminalOutput);
|
|
|
|
delete _ttyListener; _ttyListener = 0;
|
|
}
|
|
|
|
bool SeerConsoleWidget::isTerminalCreated () const {
|
|
|
|
if (_ttyFD >= 0) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool SeerConsoleWidget::isTerminalConnected () const {
|
|
|
|
if (_ttyListener != nullptr) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void SeerConsoleWidget::resetTerminal () {
|
|
|
|
if (isTerminalConnected()) {
|
|
disconnectTerminal();
|
|
}
|
|
|
|
if (isTerminalCreated() == true) {
|
|
deleteTerminal();
|
|
}
|
|
|
|
createTerminal();
|
|
}
|
|
|
|
const QString& SeerConsoleWidget::terminalDeviceName () const {
|
|
return _terminalDeviceName;
|
|
}
|
|
|
|
void SeerConsoleWidget::setScrollLines (int count) {
|
|
|
|
textEdit->setMaximumBlockCount(count);
|
|
}
|
|
|
|
int SeerConsoleWidget::scrollLines () const {
|
|
|
|
return textEdit->maximumBlockCount();
|
|
}
|
|
|
|
void SeerConsoleWidget::enableStdout (bool flag) {
|
|
|
|
_enableStdout = flag;
|
|
|
|
stdoutCheckBox->setChecked(_enableStdout);
|
|
}
|
|
|
|
bool SeerConsoleWidget::isStdoutEnabled () const {
|
|
return _enableStdout;
|
|
}
|
|
|
|
void SeerConsoleWidget::enableWrap (bool flag) {
|
|
|
|
_enableWrap = flag;
|
|
|
|
wrapTextCheckBox->setChecked(_enableWrap);
|
|
}
|
|
|
|
bool SeerConsoleWidget::isWrapEnabled () const {
|
|
return _enableWrap;
|
|
}
|
|
|
|
void SeerConsoleWidget::resetSize () {
|
|
|
|
// If there's a parent, don't reset the size.
|
|
// This means the console is attached in the
|
|
// tab bar and its size has been shrunk. We
|
|
// only want to resize if the console
|
|
// has been detached, ie: no parent.
|
|
if (parent() != 0) {
|
|
return;
|
|
}
|
|
|
|
QSettings settings;
|
|
|
|
settings.beginGroup("consolewindow"); {
|
|
resize(settings.value("size", QSize(800, 600)).toSize());
|
|
} settings.endGroup();
|
|
}
|
|
|
|
void SeerConsoleWidget::writeSettings() {
|
|
|
|
QSettings settings;
|
|
|
|
settings.beginGroup("consolewindow"); {
|
|
settings.setValue("stdout", isStdoutEnabled());
|
|
settings.setValue("wrap", isWrapEnabled());
|
|
}settings.endGroup();
|
|
}
|
|
|
|
void SeerConsoleWidget::writeFontSettings() {
|
|
|
|
QSettings settings;
|
|
|
|
settings.beginGroup("consolewindow"); {
|
|
settings.setValue("font", textEdit->font().toString());
|
|
}settings.endGroup();
|
|
}
|
|
|
|
void SeerConsoleWidget::writeSizeSettings() {
|
|
|
|
// If there's a parent, don't save the size.
|
|
// This means the console is attached in the
|
|
// tab bar and its size has been shrunk. We
|
|
// only want to save resizes if the console
|
|
// has been detached, ie: no parent.
|
|
if (parent() != 0) {
|
|
return;
|
|
}
|
|
|
|
QSettings settings;
|
|
|
|
settings.beginGroup("consolewindow"); {
|
|
settings.setValue("size", size());
|
|
}settings.endGroup();
|
|
}
|
|
|
|
void SeerConsoleWidget::readSettings() {
|
|
|
|
QSettings settings;
|
|
|
|
settings.beginGroup("consolewindow"); {
|
|
|
|
resize(settings.value("size", QSize(800, 600)).toSize());
|
|
enableStdout(settings.value("stdout", false).toBool());
|
|
enableWrap(settings.value("wrap", false).toBool());
|
|
|
|
QFont f;
|
|
if (settings.contains("font")) {
|
|
|
|
f.fromString(settings.value("font").toString());
|
|
|
|
}else{
|
|
f = QFont("monospace", 10);
|
|
}
|
|
|
|
// Get current format for the font name.
|
|
QTextCharFormat format = textEdit->currentCharFormat();
|
|
|
|
format.setFont(f);
|
|
|
|
textEdit->setCurrentCharFormat(format);
|
|
textEdit->setFont(f);
|
|
|
|
} settings.endGroup();
|
|
}
|
|
|
|
void SeerConsoleWidget::resizeEvent (QResizeEvent* event) {
|
|
|
|
// Write window settings.
|
|
writeSizeSettings();
|
|
|
|
QWidget::resizeEvent(event);
|
|
}
|
|
|
|
void SeerConsoleWidget::showEvent (QShowEvent* event) {
|
|
|
|
// Handle the event as normal.
|
|
QWidget::showEvent(event);
|
|
|
|
// Set focus on the text input.
|
|
stdinLineEdit->setFocus();
|
|
|
|
// Announce we are now visable.
|
|
emit newTextViewed();
|
|
}
|
|
|