2025-11-28 04:44:18 +01:00
|
|
|
/***************************************************************************
|
|
|
|
|
* Copyright (C) 2025 by Nicolas Keita - nicolaskeita2@@gmail.com *
|
|
|
|
|
* *
|
|
|
|
|
* This program is free software; you can redistribute it and/or modify *
|
|
|
|
|
* it under the terms of the GNU General Public License as published by *
|
|
|
|
|
* the Free Software Foundation; either version 2 of the License, or *
|
|
|
|
|
* (at your option) any later version. *
|
|
|
|
|
* *
|
|
|
|
|
* This program is distributed in the hope that it will be useful, *
|
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
|
|
|
|
* GNU General Public License for more details. *
|
|
|
|
|
* *
|
|
|
|
|
* You should have received a copy of the GNU General Public License *
|
|
|
|
|
* along with this program; if not, write to the *
|
|
|
|
|
* Free Software Foundation, Inc., *
|
|
|
|
|
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
|
|
|
|
|
***************************************************************************/
|
|
|
|
|
|
|
|
|
|
#include <QPointer>
|
|
|
|
|
#include <QTcpSocket>
|
|
|
|
|
#include <QTimer>
|
|
|
|
|
#include <QHostAddress>
|
|
|
|
|
#include <QDebug>
|
|
|
|
|
|
|
|
|
|
#include "TelnetServerStub.h"
|
|
|
|
|
#include "utils.h"
|
|
|
|
|
|
infrastructure: use std::chrono literals for time durations (#9493)
#### Brief overview of PR changes/additions
Convert raw millisecond integer literals at time-duration call sites to
`std::chrono` literals, and add `#include <chrono>` to each touched
translation unit. Examples:
- `QTimer::singleShot(0, ...)` → `QTimer::singleShot(0ms, ...)`
- `mpTimerReplay->setInterval(1000)` → `setInterval(1s)`
- `mPendingTimer.start(60000)` → `start(1min)`
- `QObject::startTimer(50)` → `startTimer(50ms)`
- `QTest::qWait(100)` → `QTest::qWait(100ms)`
- `QThread::msleep(10)` → `QThread::sleep(10ms)`
This is a semantics-preserving refactor - every duration is kept exactly
equal to before (e.g. `1000` ms becomes `1s`, `60000` ms becomes
`1min`). No behavioural change.
#### Motivation for adding to Mudlet
Chrono literals make time durations self-documenting and type-safe. `1s`
/ `100ms` read unambiguously where a bare `1000` / `100` forces the
reader to remember each API's unit, and the compiler now rejects unit
mismatches. Only genuine duration arguments were converted - loop
counts, scroll-line counts, sizes, ports and the like were deliberately
left as plain integers.
All targeted APIs provide `std::chrono` overloads in the minimum
supported Qt (6.8.2): `QTimer::singleShot`/`start`/`setInterval` (5.8),
`QObject::startTimer` (5.9), `QThread::sleep(std::chrono::nanoseconds)`
(6.6) and `QTest::qWait(std::chrono::milliseconds)` (6.7).
#### Other info (issues closed, discussion etc)
Test case: the full application builds cleanly and the entire functional
`ctest` suite passes. The only failing test is the known, pre-existing
`PasswordMigrationTest` LSan exit-leak (GTK3/fontconfig noise), which is
unrelated to this change.
Assisted-by: Claude:claude-opus-4-8
2026-07-25 20:24:31 +02:00
|
|
|
#include <chrono>
|
|
|
|
|
|
|
|
|
|
using namespace std::chrono_literals;
|
|
|
|
|
|
2025-11-28 04:44:18 +01:00
|
|
|
TelnetServerStub::TelnetServerStub(QObject* parent)
|
2026-08-13 13:20:12 +02:00
|
|
|
: QTcpServer(parent)
|
2025-11-28 04:44:18 +01:00
|
|
|
{
|
|
|
|
|
connect(this, &QTcpServer::newConnection, this, &TelnetServerStub::onNewConnection);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void TelnetServerStub::start(const QString& host, quint16 port)
|
|
|
|
|
{
|
2026-01-06 12:07:35 +01:00
|
|
|
Q_UNUSED(host)
|
|
|
|
|
const QHostAddress addr = QHostAddress::LocalHost;
|
|
|
|
|
if (listen(addr, port)) {
|
2026-07-20 09:01:51 +02:00
|
|
|
qInfo().noquote() << qsl("✅ TelnetServerStub listening on %1:%2").arg(addr.toString()).arg(serverPort());
|
2025-11-28 04:44:18 +01:00
|
|
|
} else {
|
2026-01-06 12:07:35 +01:00
|
|
|
qCritical().noquote() << qsl("❌ Failed to start TelnetServerStub: %1").arg(errorString());
|
2025-11-28 04:44:18 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-13 13:20:12 +02:00
|
|
|
TelnetServerStub::~TelnetServerStub()
|
|
|
|
|
{
|
|
|
|
|
if (!mPendingData.isEmpty()) {
|
|
|
|
|
qWarning().noquote() << qsl("⚠️ TelnetServerStub destroyed with %1 undelivered queued bytes - no client was ever accepted.").arg(mPendingData.size());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
add option to undo the game's own line wrapping (#9455)
https://github.com/user-attachments/assets/49e8a176-899c-43fd-a931-6e04ab5b5efb
#### Brief overview of PR changes/additions
New opt-in profile option that rejoins lines the game hard-wrapped
itself, so triggers see the whole logical line and Mudlet's own wrapping
handles display. Enable in Settings → Main display, or
`setConfig("undoServerWrap", true)`. Prompts, blank lines, MXP `<br>`
and script-fed text never join, and prose/indentation gates keep ASCII
art, menus and tables intact. A one-time callout points the option out,
and games that look like they wrap raise a one-time hint with a
click-to-enable link.
#### Motivation for adding to Mudlet
On games that can't disable server-side wrapping, triggers need fragile
multiline patterns. This fixes it client-side - as far as we know, a
first among MUD clients.
#### Other info (issues closed, discussion etc)
Tests added.
---------
Signed-off-by: Vadim Peretokin <vadim.peretokin@mudlet.org>
2026-07-20 22:45:10 +02:00
|
|
|
void TelnetServerStub::sendRaw(const QByteArray& data)
|
|
|
|
|
{
|
|
|
|
|
if (!mpClient) {
|
2026-08-13 13:20:12 +02:00
|
|
|
if (mHadClient) {
|
|
|
|
|
// Between sessions there is no race to absorb, so queuing here
|
|
|
|
|
// would leak a dead session's bytes into the next connection:
|
|
|
|
|
qWarning() << "⚠️ sendRaw called without a connected client.";
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
// Tests wait on the client-side connected signal, which can fire
|
|
|
|
|
// before this server side has accepted the connection - dropping the
|
|
|
|
|
// bytes here loses the payload on fast runners, so hold them until
|
|
|
|
|
// onNewConnection():
|
|
|
|
|
mPendingData.append(data);
|
add option to undo the game's own line wrapping (#9455)
https://github.com/user-attachments/assets/49e8a176-899c-43fd-a931-6e04ab5b5efb
#### Brief overview of PR changes/additions
New opt-in profile option that rejoins lines the game hard-wrapped
itself, so triggers see the whole logical line and Mudlet's own wrapping
handles display. Enable in Settings → Main display, or
`setConfig("undoServerWrap", true)`. Prompts, blank lines, MXP `<br>`
and script-fed text never join, and prose/indentation gates keep ASCII
art, menus and tables intact. A one-time callout points the option out,
and games that look like they wrap raise a one-time hint with a
click-to-enable link.
#### Motivation for adding to Mudlet
On games that can't disable server-side wrapping, triggers need fragile
multiline patterns. This fixes it client-side - as far as we know, a
first among MUD clients.
#### Other info (issues closed, discussion etc)
Tests added.
---------
Signed-off-by: Vadim Peretokin <vadim.peretokin@mudlet.org>
2026-07-20 22:45:10 +02:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
mpClient->write(data);
|
|
|
|
|
mpClient->flush();
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-28 04:44:18 +01:00
|
|
|
void TelnetServerStub::onNewConnection()
|
|
|
|
|
{
|
|
|
|
|
QTcpSocket* client = nextPendingConnection();
|
|
|
|
|
|
|
|
|
|
if (!client) {
|
|
|
|
|
qWarning() << "⚠️ onNewConnection called but no pending connection.";
|
|
|
|
|
return;
|
|
|
|
|
}
|
add option to undo the game's own line wrapping (#9455)
https://github.com/user-attachments/assets/49e8a176-899c-43fd-a931-6e04ab5b5efb
#### Brief overview of PR changes/additions
New opt-in profile option that rejoins lines the game hard-wrapped
itself, so triggers see the whole logical line and Mudlet's own wrapping
handles display. Enable in Settings → Main display, or
`setConfig("undoServerWrap", true)`. Prompts, blank lines, MXP `<br>`
and script-fed text never join, and prose/indentation gates keep ASCII
art, menus and tables intact. A one-time callout points the option out,
and games that look like they wrap raise a one-time hint with a
click-to-enable link.
#### Motivation for adding to Mudlet
On games that can't disable server-side wrapping, triggers need fragile
multiline patterns. This fixes it client-side - as far as we know, a
first among MUD clients.
#### Other info (issues closed, discussion etc)
Tests added.
---------
Signed-off-by: Vadim Peretokin <vadim.peretokin@mudlet.org>
2026-07-20 22:45:10 +02:00
|
|
|
mpClient = client;
|
2026-08-13 13:20:12 +02:00
|
|
|
mHadClient = true;
|
2025-11-28 04:44:18 +01:00
|
|
|
qInfo().noquote() << qsl("🔌 Client connected: %1").arg(client->peerAddress().toString());
|
|
|
|
|
|
2026-08-13 13:20:12 +02:00
|
|
|
if (!mPendingData.isEmpty()) {
|
|
|
|
|
const auto bytesWritten = client->write(mPendingData);
|
|
|
|
|
client->flush();
|
|
|
|
|
if (bytesWritten <= 0) {
|
|
|
|
|
qWarning().noquote() << qsl("⚠️ Failed to deliver %1 queued bytes to %2").arg(QString::number(mPendingData.size()), client->peerAddress().toString());
|
|
|
|
|
}
|
|
|
|
|
mPendingData.clear();
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-28 04:44:18 +01:00
|
|
|
QPointer<QTcpSocket> safeClient = client;
|
|
|
|
|
|
2026-08-13 13:20:12 +02:00
|
|
|
QTimer::singleShot(100ms, [safeClient, welcomeMessage = mpWelcomeMessage]() {
|
2025-11-28 04:44:18 +01:00
|
|
|
if (!safeClient) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
const auto bytesWritten = safeClient->write(welcomeMessage.toUtf8() + "\r\n");
|
|
|
|
|
safeClient->flush();
|
|
|
|
|
if (bytesWritten <= 0) {
|
2026-08-13 13:20:12 +02:00
|
|
|
qWarning().noquote() << qsl("⚠️ Failed to send welcome message to %1").arg(safeClient->peerAddress().toString());
|
2025-11-28 04:44:18 +01:00
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
2026-08-13 13:20:12 +02:00
|
|
|
connect(client, &QTcpSocket::disconnected, [safeClient]() {
|
2025-11-28 04:44:18 +01:00
|
|
|
if (!safeClient) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
qInfo().noquote() << qsl("Client disconnected: %1").arg(safeClient->peerAddress().toString());
|
|
|
|
|
safeClient->deleteLater();
|
|
|
|
|
});
|
|
|
|
|
}
|