diff --git a/.gitignore b/.gitignore index f147edf..fab7372 100644 --- a/.gitignore +++ b/.gitignore @@ -1,52 +1,73 @@ -# C++ objects and libs -*.slo -*.lo -*.o +# This file is used to ignore files which are generated +# ---------------------------------------------------------------------------- + +*~ +*.autosave *.a -*.la -*.lai +*.core +*.moc +*.o +*.obj +*.orig +*.rej *.so *.so.* -*.dll -*.dylib - -# Qt-es -object_script.*.Release -object_script.*.Debug -*_plugin_import.cpp +*_pch.h.cpp +*_resource.rc +*.qm +.#* +*.*# +core +!core/ +tags +.DS_Store +.directory +*.debug +Makefile* +*.prl +*.app +moc_*.cpp +ui_*.h +qrc_*.cpp +Thumbs.db +*.res +*.rc /.qmake.cache /.qmake.stash -*.pro.user -*.pro.user.* -*.qbs.user -*.qbs.user.* -*.moc -moc_*.cpp -moc_*.h -qrc_*.cpp -ui_*.h -*.qmlc -*.jsc -Makefile* -*build-* -*.qm -*.prl -# Qt unit tests -target_wrapper.* +# qtcreator generated files +*.pro.user* -# QtCreator -*.autosave +# xemacs temporary files +*.flc -# QtCreator Qml -*.qmlproject.user -*.qmlproject.user.* +# Vim temporary files +.*.swp -# QtCreator CMake -CMakeLists.txt.user* +# Visual Studio generated files +*.ib_pdb_index +*.idb +*.ilk +*.pdb +*.sln +*.suo +*.vcproj +*vcproj.*.*.user +*.ncb +*.sdf +*.opensdf +*.vcxproj +*vcxproj.* -# QtCreator 4.8< compilation database -compile_commands.json +# MinGW generated files +*.Debug +*.Release + +# Python byte code +*.pyc + +# Binaries +# -------- +*.dll +*.exe -# QtCreator local machine specific files for imported projects -*creator.user* diff --git a/README.md b/README.md index 8005ede..68d87e5 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,16 @@ # qtwebsockettester A simple graphical tool to test websocket connections + +![Screenshot](/screenshot.png) + +## Build and Run +``` +git clone git@github.com:0xFEEDC0DE64/qtwebsockettester.git +cd qtwebsockettester +mkdir build +cd build +qmake .. +make +./qtwebsockettester +``` + diff --git a/main.cpp b/main.cpp new file mode 100644 index 0000000..79abbe6 --- /dev/null +++ b/main.cpp @@ -0,0 +1,13 @@ +#include + +#include "mainwindow.h" + +int main(int argc, char *argv[]) +{ + QApplication app{argc, argv}; + + MainWindow mainWindow; + mainWindow.show(); + + return app.exec(); +} diff --git a/mainwindow.cpp b/mainwindow.cpp new file mode 100644 index 0000000..65b5179 --- /dev/null +++ b/mainwindow.cpp @@ -0,0 +1,138 @@ +#include "mainwindow.h" +#include "ui_mainwindow.h" + +#include +#include + +// utilities +namespace { +template +auto qtEnumToString(const QEnum value) +{ + return QMetaEnum::fromType().valueToKey(value); +} +} // namespace + +MainWindow::MainWindow(QWidget *parent) : + QMainWindow{parent}, + m_ui{std::make_unique()}, + m_webSocket{QString{}, QWebSocketProtocol::VersionLatest, this} +{ + m_ui->setupUi(this); + + connect(m_ui->lineEditUrl, &QLineEdit::returnPressed, this, &MainWindow::connectClicked); + connect(m_ui->pushButtonConnect, &QAbstractButton::clicked, this, &MainWindow::connectClicked); + + connect(m_ui->lineEditSend, &QLineEdit::returnPressed, this, &MainWindow::sendClicked); + connect(m_ui->pushButtonSend, &QAbstractButton::clicked, this, &MainWindow::sendClicked); + + connect(&m_webSocket, &QWebSocket::connected, this, &MainWindow::connected); + connect(&m_webSocket, &QWebSocket::disconnected, this, &MainWindow::disconnected); + connect(&m_webSocket, &QWebSocket::stateChanged, this, &MainWindow::stateChanged); + connect(&m_webSocket, &QWebSocket::textMessageReceived, this, &MainWindow::textMessageReceived); + connect(&m_webSocket, &QWebSocket::binaryMessageReceived, this, &MainWindow::binaryMessageReceived); + connect(&m_webSocket, qOverload(&QWebSocket::error), this, &MainWindow::error); + connect(&m_webSocket, &QWebSocket::pong, this, &MainWindow::pong); + + stateChanged(m_webSocket.state()); +} + +MainWindow::~MainWindow() = default; + +void MainWindow::connectClicked() +{ + if (m_webSocket.state() == QAbstractSocket::UnconnectedState) + { + const auto url = QUrl::fromUserInput(m_ui->lineEditUrl->text()); + if (url.isEmpty()) + { + QMessageBox::warning(this, tr("Invalid url entered!"), tr("Invalid url entered!")); + return; + } + + if (url.scheme().toLower() != "ws" && + url.scheme().toLower() != "wss") + { + QMessageBox::warning(this, tr("Invalid url entered!"), tr("Only urls starting with ws:// or wss:// are allowed!")); + return; + } + + m_ui->plainTextEdit->appendHtml(QStringLiteral("%0 %1
") + .arg(QTime::currentTime().toString()) + .arg(tr("Connecting to %0").arg(url.toString()))); + + m_webSocket.open(url); + } + else + m_webSocket.close(); +} + +void MainWindow::sendClicked() +{ + if (m_webSocket.state() != QAbstractSocket::ConnectedState) + { + QMessageBox::warning(this, tr("WebSocket not connected!"), tr("WebSocket not connected!")); + return; + } + + const auto msg = m_ui->lineEditSend->text(); + m_webSocket.sendTextMessage(msg); + m_ui->lineEditSend->clear(); + + m_ui->plainTextEdit->appendHtml(QStringLiteral("%0 %2: %3
") + .arg(QTime::currentTime().toString()) + .arg("red") + .arg(tr("SEND")) + .arg(msg)); +} + +void MainWindow::connected() +{ + m_ui->plainTextEdit->appendHtml(QStringLiteral("%0 %1
") + .arg(QTime::currentTime().toString()) + .arg(tr("Connected"))); +} + +void MainWindow::disconnected() +{ + m_ui->plainTextEdit->appendHtml(QStringLiteral("%0 %1
") + .arg(QTime::currentTime().toString()) + .arg(tr("Disconnected"))); +} + +void MainWindow::stateChanged(QAbstractSocket::SocketState state) +{ + m_ui->lineEditUrl->setEnabled(state == QAbstractSocket::UnconnectedState); + m_ui->pushButtonConnect->setText(state == QAbstractSocket::UnconnectedState ? tr("Connect") : tr("Disconnect")); + m_ui->labelStatus->setText(qtEnumToString(state)); + m_ui->lineEditSend->setEnabled(state == QAbstractSocket::ConnectedState); + m_ui->pushButtonSend->setEnabled(state == QAbstractSocket::ConnectedState); +} + +void MainWindow::textMessageReceived(const QString &message) +{ + m_ui->plainTextEdit->appendHtml(QStringLiteral("%0 %2: %3
") + .arg(QTime::currentTime().toString()) + .arg("green") + .arg(tr("RECV")) + .arg(message)); +} + +void MainWindow::binaryMessageReceived(const QByteArray &message) +{ + m_ui->plainTextEdit->appendHtml(QStringLiteral("%0 %2: %3
") + .arg(QTime::currentTime().toString()) + .arg("blue") + .arg(tr("RECV")) + .arg(tr("<BINARY>"))); +} + +void MainWindow::error(QAbstractSocket::SocketError error) +{ + QMessageBox::warning(this, tr("WebSocket error occured!"), tr("WebSocket error occured!") + "\n\n" + qtEnumToString(error)); +} + +void MainWindow::pong(quint64 elapsedTime, const QByteArray &payload) +{ + qDebug() << "pong" << elapsedTime; +} diff --git a/mainwindow.h b/mainwindow.h new file mode 100644 index 0000000..dc4da40 --- /dev/null +++ b/mainwindow.h @@ -0,0 +1,33 @@ +#pragma once + +#include +#include + +#include + +namespace Ui { class MainWindow; } + +class MainWindow : public QMainWindow +{ + Q_OBJECT + +public: + explicit MainWindow(QWidget *parent = nullptr); + ~MainWindow(); + +private slots: + void connectClicked(); + void sendClicked(); + + void connected(); + void disconnected(); + void stateChanged(QAbstractSocket::SocketState state); + void textMessageReceived(const QString &message); + void binaryMessageReceived(const QByteArray &message); + void error(QAbstractSocket::SocketError error); + void pong(quint64 elapsedTime, const QByteArray &payload); + +private: + const std::unique_ptr m_ui; + QWebSocket m_webSocket; +}; diff --git a/mainwindow.ui b/mainwindow.ui new file mode 100644 index 0000000..0d7fbc8 --- /dev/null +++ b/mainwindow.ui @@ -0,0 +1,70 @@ + + + MainWindow + + + + 0 + 0 + 800 + 600 + + + + WebSocket Tester + + + + + + + + + ws://localhost:1234/path/to/ws + + + + + + + Connect + + + + + + + TextLabel + + + + + + + + + + + + + + Send + + + + + + + + + true + + + + + + + + + + diff --git a/qtwebsockettester.pro b/qtwebsockettester.pro new file mode 100644 index 0000000..01827d7 --- /dev/null +++ b/qtwebsockettester.pro @@ -0,0 +1,15 @@ +QT = core gui widgets network websockets + +CONFIG += c++latest + +DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 + +SOURCES += \ + main.cpp \ + mainwindow.cpp + +FORMS += \ + mainwindow.ui + +HEADERS += \ + mainwindow.h diff --git a/screenshot.png b/screenshot.png new file mode 100644 index 0000000..58838f9 Binary files /dev/null and b/screenshot.png differ