Commit graph

284 commits

Author SHA1 Message Date
Vadim Peretokin
ed403cabc1
infrastructure: keep the profile-removal file list from going stale (#9739)
#### Brief overview of PR changes/additions
- Hoists the list `slot_deleteProfile()` checks a never-played profile
against out of the function to
`dlgConnectionProfiles::scmConnectionDetailFiles`, beside the file's
other `scm*` constants, and points at it from both `writeProfileData()`
implementations.
- Adds a `ProfileDeletionSafetyTest` case that sets a profile up through
the real dialog (New profile, name it, fill the connection form in,
re-select it) and fails if anything it wrote is missing from the list.
- Pins the deliberate exclusions too: a profile holding a stored
password or a typed-in character name still asks before removal.

#### Motivation for adding to Mudlet
Nothing linked that list to the ~15 places profile data gets written, so
it could silently go stale; because it is an allowlist a stale entry
only ever costs an extra confirmation prompt, but the maintenance trap
was worth closing.

#### Other info (issues closed, discussion etc)
Follows up
https://github.com/Mudlet/Mudlet/pull/9722#discussion_r3740581754 on
#9722 (fix: a profile named "." or ".." deletes every profile when
removed). No behaviour change.

**Test case:** `ctest -R ProfileDeletionSafetyTest` (20 cases). Removing
an entry from the constant makes it fail naming the file; adding `login`
makes the character-name case fail.

Assisted-by: Claude:claude-opus-5
2026-08-08 17:30:26 +00:00
Vadim Peretokin
045a256158
fix: a profile named "." or ".." deletes every profile when removed (#9722)
#### Brief overview of PR changes/additions
- Validation: a typed profile name must be a folder of its own - rejects
a lone `.` and anything containing `..`. Folders already on disk stay
exempt
- Containment: `reallyDeleteProfile()` refuses any path that is not a
direct child of `profiles/`, and now checks `removeRecursively()`
instead of failing mute
- Confirmation: the "nothing to delete" shortcut no longer fires when a
map, stored password or dictionary is present

#### Motivation for adding to Mudlet
A profile named `.` or `..` turned **Remove** into a wipe: `.` resolves
to `profiles/`, `..` to the whole `~/.config/mudlet`. The name was
accepted with no error, and the confirmation was skipped because a fresh
profile looks empty - two clicks deep on the first screen every user
sees.

#### Other info (issues closed, discussion etc)
Pre-existing, not a 5.0 regression - shipped 4.22.0 behaves identically.
Dots have been allowed deliberately since 2011 (`ee1fd051c`), and
`Achaea 2.0` keeps working.

**Test case:** name a new profile `.` and press Remove - previously
every profile was deleted with no prompt, now the name is refused.

New `ProfileDeletionSafetyTest` drives the real dialog against a
temporary config dir, plus `profileFolderPath`/`profileNameUsableAsIs`
rows in `ProfileNameValidationTest`. 81/81 ctest pass.

Assisted-by: Claude:claude-opus-5
2026-08-08 08:30:14 +02:00
Vadim Peretokin
159d4bbe02
Fix three crashes in the game selection screen (#9702)
#### Brief overview of PR changes/additions

- **Right-clicking the games list with nothing selected killed Mudlet.**
`dlgConnectionProfiles::slot_profileContextMenu()` dereferenced
`currentItem()` unguarded. That line is byte-identical in 4.22.0, so the
null deref itself is long-standing and latent - what is new is that it
became reachable: "improve: split the games list into My games and All
games tabs" (#9452) leaves a user with no saved profiles an empty but
still right-clickable "My games" tab, a state 4.22.0's always-populated
list never had. About 40 seconds into a fresh install.
- **Copying a profile while the list was rebuilt was a use-after-free.**
The copy runs on a thread pool and its completion handler kept the
`QListWidgetItem*` it had made; clicking the other games tab meanwhile
calls `fillout_form()`, which destroys every item. The handler now finds
the copy by name, and the `QFutureWatcher` is parented so it cannot
outlive the dialog.
- **Quitting before the connection dialog had been shown dereferenced
null.** The queued `0ms` lambda in `mudlet::slot_showConnectionDialog()`
used `mpConnectionDialog`, which `mudlet::closeEvent()` closes (it is
`WA_DeleteOnClose`) and clears.

#### Motivation for adding to Mudlet

All three came out of the 5.0 QA sweep and are confirmed with
AddressSanitizer. The first is the serious one - it is the default state
of a brand-new install, so a new user can lose Mudlet before they have
connected to anything.

Scope note on the third: it is **not** a 5.0 regression. It has been
there since "Fix: Improve tab indicators and detached window UX" (#7965)
and is unchanged in 4.22.0; #9493 only turned the literal `0` into
`0ms`. Nor could I reach it by clicking: I drove *Games -> Close
profile* followed by quitting at six delays from 0 to 2000 ms and the
dialog was always painted first. It reproduces deterministically
in-process, and QA reproduced it 2/2 driving the close from Lua. Worth
guarding - the pointer is documented to go null - but latent rather than
routinely hit.

#### Other info (issues closed, discussion etc)

Test case: `ctest -R ConnectionDialogCrashTest` - with the fix reverted,
four of its tests reproduce the original ASan reports exactly (two SEGVs
in `slot_profileContextMenu`, a heap-use-after-free in
`slot_itemClicked`, the SEGV in `QWidget::show()` from the lambda); two
more are controls that pass either way, one of them pinning that the
menu still opens for a selected profile so the guard cannot degenerate
into an unconditional early return. Full suite 79/79.

Assisted-by: Claude:claude-opus-5
2026-08-07 10:14:30 +02:00
Vadim Peretokin
bb2ead764c
fix: profiles with parentheses in their name can be opened again (#9610)
#### Brief overview of PR changes/additions
- Parentheses are now allowed in profile names, matching the "name (2)"
folders file managers create when copying
- Selecting a profile folder made outside Mudlet no longer silently
mangles its name and renames the folder on disk
- Adds ProfileNameValidationTest for the name validation rules and
ProfileFolderNameTest, which drives the real connection dialog against
externally-created folders (fails without the fix)

#### Motivation for adding to Mudlet
Merely selecting a profile folder like "test (2)" mangled its name to
"test 2" and silently renamed the folder on disk, and the reporter could
not launch the profile at all.

#### Other info (issues closed, discussion etc)
Assisted-by: Claude:claude-fable-5
Assisted-by: Claude:claude-opus-5
Signed-off-by: Vadim Peretokin <vadim.peretokin@mudlet.org>

**Test case:** Copy a profile folder to "test (2)" while Mudlet is
closed, reopen - the profile keeps its name, Connect/Offline are
enabled, and it opens.

#### Demo


https://github.com/user-attachments/assets/25b1bb4c-eaa7-4079-8b8e-a12de3ebb215
2026-08-04 16:09:42 +02:00
Vadim Peretokin
e52432fe5f
fix: removing a game's profile no longer hides it from the All Games list (#9608)
#### Brief overview of PR changes/additions
- Deleting a profile of a pre-installed game now only removes the
profile - the game stays available in All Games
- Games hidden by this bug in the past reappear automatically
- Adds DefaultGameDeleteTest covering both cases

#### Motivation for adding to Mudlet
Removing a game from My Games unexpectedly removed it from the All Games
catalog permanently.

#### Other info (issues closed, discussion etc)
Assisted-by: Claude:claude-fable-5
Assisted-by: Claude:claude-opus-5
Signed-off-by: Vadim Peretokin <vadim.peretokin@mudlet.org>

**Test case:** Play a game from All Games, close and reopen Mudlet,
remove it from My Games - it still appears under All Games.

#### Demo


https://github.com/user-attachments/assets/6df6f2bd-d0d4-446f-80de-8c4c190dbf45
2026-08-04 12:00:37 +02:00
Vadim Peretokin
295b000231
improve: make the New profile button show what it did (#9607)
#### Brief overview of PR changes/additions
- Clicking New now shows the new profile at the top of the list with an
icon, selected and scrolled into view
- The profile selection window no longer grows a little every time it
switches views
- Skipping the tutorial invitation no longer triggers a needless window
resize

#### Motivation for adding to Mudlet
New users clicking New saw the window resize and nothing else happen,
leaving them unable to add a game manually.

#### Other info (issues closed, discussion etc)
Assisted-by: Claude:claude-fable-5

**Test case:** Open the profile selector, click New - a highlighted "new
profile name" entry appears at the top with the name field focused, and
the window size does not change.

#### Demo


https://github.com/user-attachments/assets/5789738a-52fb-492d-89d4-b68cb0f36aff
2026-08-03 14:10:57 +02:00
Vadim Peretokin
6c991a1708
infrastructure: make windows safe to destroy while a field has the text cursor (#9596)
#### Brief overview of PR changes/additions
- New `utils::disconnectChildSignals()`, called by the destructors of
the connection dialog, the preferences and the editor: a window stops
listening to its own widgets before it goes away.
- Covers the reported case (connection dialog `Profile name` field) plus
the same exposure found in the preferences (MMCP chat name, shortcut
editors) and the editor (item name, command, pattern and sound file
fields). The preferences and the editor had no destructor at all before
this.
- New `DialogTeardownTest` covering all three windows, plus a canary
that fails if a future Qt stops emitting the focus-out signals the whole
thing rests on.

#### Motivation for adding to Mudlet
Destroying one of these windows while the text cursor sits in one of its
fields aborts the run - which is how #9574 turned up, in a functional
test - and the windows should simply be safe to destroy, rather than
safe only along the `close()` paths that happen to hide them first.

#### Other info (issues closed, discussion etc)
Closes #9574.

The mechanism: a visible window is taken off the screen while its
base-class destructors unwind (`~QDialog` hides it, `~QWidget` closes
any other window class). That moves the keyboard focus off the field
holding it, the field reports `editingFinished()`, and Qt delivers that
to a slot of an object whose derived part is already gone:

```
ASSERT failure in dlgConnectionProfiles: "Called object is not of the correct type (class destructor may have already run)"
```

**How much of this can a player hit today: as far as I can trace, none
of it**, which is why there are no crash reports behind this:

- Every production teardown goes through `close()` / `accept()` /
`reject()` first, and that hide happens while the object is still whole
- so the field's `editingFinished()` is delivered normally and the edit
is saved, exactly as before. `Host::closeChildren()` closes the editor
that way, `mudlet::closeEvent()` closes the connection dialog that way.
- Nothing `delete`s or `deleteLater()`s these three windows directly.
- At exit `main()` deletes the QApplication, which destroys platform
windows without running widget destructors, so the preferences dialog -
the one window nothing explicitly closes - is never destructed either.
- The assert is a `Q_ASSERT_X`, and since we never set
`CMAKE_BUILD_TYPE`, Qt defines `QT_NO_DEBUG` for our builds and compiles
it out. A shipped build would not abort at that point; it would run the
slot against destroyed members instead, which is undefined behaviour
that can quietly rename a profile or a trigger.

So this is a latent trap rather than a live player crash: it fires today
in the test suite, and it fires the moment any future code destroys one
of these windows while it is on screen. The fix is small enough to be
worth taking on those terms.

Verified with standalone Qt probes: `QLineEdit` emits once anything has
written to it (`setText()` is enough, even with an empty string),
`QAbstractSpinBox` and `QKeySequenceEdit` emit unconditionally, and
plain child widgets such as the editor's `dlg*MainArea` panels are not
exposed - their slots still run while they are alive.

`test/functional_tests/DialogTeardownTest.cpp` is formatted with the
repo's clang-format, which the older tests next to it predate.

**Test case:** `ctest -R DialogTeardownTest`. All three cases abort on
`development` with the assert above and pass here. There is no manual
GUI reproduction - see the tracing above.
2026-08-03 11:40:11 +02:00
Vadim Peretokin
0f9bc0a4be
infrastructure: decouple the mapper engine from UI dialogs (#9513)
#### Brief overview of PR changes/additions
- Removes all raw Qt Widgets usage from the map engine: `TMap.{h,cpp}`
no longer owns a `QProgressDialog` (and drops a dead `QFileDialog`
include), and `XMLimport.{h,cpp}` no longer pulls in `QApplication` (the
clipboard read now uses `QGuiApplication::clipboard()`, which lives in
Qt Gui).
- The standalone map-progress dialog (shown when the mapper is not
visible, for map download / XML import and JSON export/import) is now
driven by Qt signals carrying pre-translated payloads; the frontend
(`TMainConsole`) owns the actual `QProgressDialog`, and a user cancel
returns to the engine through `TMap::slot_mapProgressDialogCancelled()`.
- Adds `MapProgressDialogSeamTest` covering the transfer-progress state
machine, a JSON export/import round trip driving the new signals, a
mid-import cancel delivered through the seam (the highest-risk change,
since the JSON reader used to poll `QProgressDialog::wasCanceled()`
synchronously), and an XML map import re-entered from inside a running
JSON operation.

#### Motivation for adding to Mudlet
Second concrete step of the re-scoped libmudlet plan (a Qt-Widgets-free
`mudlet_core` for headless use, testability and WASM). It copies the
seam template established in #9507: core emits a pre-translated payload
-> frontend owns the widget -> a callback slot returns the answer. The
Qt Widgets dependency audit (`cmake/audit-core-widgets.sh`) drops from
**151 to 147** offending files; `TMap.cpp`, `TMap.h`, `XMLimport.cpp`
and `XMLimport.h` are all now clean. The mapper-owned inline progress
path (`dlgMapper`/`T2DMap`, Mudlet's own widgets) is deliberately
untouched here - those move wholesale in the later target-split phase.

#### Other info (issues closed, discussion etc)
Part of #8681 / #9011. Existing translations are unaffected: every
progress string keeps its `TMap` `tr()` context, so current translations
carry straight over. Two new strings do arrive, both with `//:`
translator comments - the warnings shown when a map download or an XML
map import is refused because a JSON import/export is already running.
The JSON dialog stays non-modal and the download/import dialog keeps its
modeless styling, each applied by the frontend. The engine keeps its own
`mMapProgressStandalone` / `mMapProgressCancelRequested` /
`mMapProgressStandaloneMaximum` state to replace the widget read-backs
it used to do (`!= nullptr`, `wasCanceled()`, `maximum()`). If a map
operation ever reaches the engine before a console is wired (checked via
`isSignalConnected`), `TMap::warnIfMapProgressUnwired()` logs a loud
`qWarning` rather than silently running with no progress UI.

It also closes a latent null-dereference that exists on `development`
today. With the mapper visible a map download takes the inline-progress
path, leaving `mpProgressDialog` null - so a JSON export started
meanwhile sails past the `if (mpProgressDialog)` "already in progress"
check and creates a dialog of its own. When the download then finishes
inside the `processEvents()` pump the export is running,
`clearTransferProgress()` deletes and nulls *that* dialog, and the
export's next `incrementJsonProgressDialog()` dereferences null. The
engine now records whose dialog is up (`mMapProgressIsTransfer`) so a
transfer only ever closes its own, and `importMap()` refuses to start
while a JSON operation holds the progress - the mirror of the guard
`downloadMap()` has.

Two review-driven details worth flagging: the frontend only wires the
dialog's cancel to the engine when the operation is actually cancelable,
so a non-cancelable local XML import no longer turns a window-close into
a spurious "Map download was canceled" message; and the standalone
download/import dialog is now parented to the console (like the JSON one
always was, and like #9507's package-download dialog), so it centres on
and dies with the profile window. The three `#include <QApplication>`
additions to `Host.cpp` / `dlgTriggerEditor.cpp` /
`dlgConnectionProfiles.cpp` replace the transitive include they used to
get from `XMLimport.h`; all three are already Qt Widgets consumers, so
the audit count is unaffected.

Assisted-by: Claude:claude-opus-4-8
Assisted-by: Claude:claude-opus-5

**Test case:** With a mapper window open, use a game that supports map
download (or call `downloadMap()`) and confirm the progress dialog
shows, updates, and its Abort cancels the download. Then with the mapper
window closed, run `exportJsonMap()` and `importJsonMap()` on a large
map and confirm the non-modal JSON progress dialog appears, updates its
Areas/Rooms/Labels counts, and that clicking Abort during an import
stops it with an "aborted by user" result. Load a local XML map
(Settings -> Map -> load) and confirm closing its progress window does
not print a "Map download was canceled" line. Everything should behave
exactly as on `development`.

#### Demo (before & after)


https://github.com/user-attachments/assets/f1c62580-2d03-4e5b-a6ee-f6e2b78d113d
2026-08-02 15:33:07 +02:00
Vadim Peretokin
d4376326bb
fix: stop crashes while saving and profiles losing their triggers (#9557)
#### Brief overview of PR changes/additions
- Profile loading now only considers real `*.xml` saves: an empty
QSaveFile temporary left behind by a crash during a save can no longer
be loaded as "the profile", which made a profile open with its
connection settings intact but every trigger/script seemingly gone.
Affected profiles heal themselves on next load by falling back to the
newest real save.
- Packages that uninstall themselves from their own timer script or
event-handler script (a common auto-updater pattern) no longer free the
very objects still executing: `TimerUnit`/`ScriptUnit` uninstall now
defers deletion while `TTimer::execute()` / `Host::raiseEvent()` are on
the call stack, completing the #9337/#9383 fix that already covered
triggers/aliases/keys. Deferred timer deletes are flushed before the
queued post-uninstall save runs, so removed items cannot be serialized
back into the profile.
- `Host::saveProfile()`'s background module task no longer reads
`writers`/`saveFutures` concurrently with the main thread (data race in
the profile save path).

#### Motivation for adding to Mudlet
Fixes a real-world heap-corruption crash cluster (Sentry MUDLET-32 /
MUDLET-2S / MUDLET-48: `STATUS_HEAP_CORRUPTION` on 4.21.0/4.21.1, frames
touching lua51/Qt6Core/libpugixml, breadcrumbs showing package uninstall
activity around saves) and the profile data loss it caused.

#### Other info (issues closed, discussion etc)
Root cause of the crashes: #9111 (in the 4.20.1 → 4.21.0 window) changed
the `*Unit::uninstall()` methods from unregister-only to immediate
`delete`. A package script calling `uninstallPackage()` on its own
package then freed objects still on the call stack - use-after-free that
poisons the heap, typically detected slightly later during the
background save serialization (hence the pugixml/lua frames, aborts
mid-save, and zero-byte `....xml.XXXXXX` QSaveFile leftovers in
`current/`). #9383 fixed the trigger/alias/key cases; this completes
timers and scripts, which reproduce under ASan on current development
(heap-use-after-free in `Tree<TScript>::isActive()` /
`TTimer::execute()`).

Data-loss mechanism (generic): a crash mid-save leaves a 0-byte
QSaveFile temporary as the newest file in `current/`;
`mudlet::loadProfile()` picked the newest file of any name, tried to
load the empty temp, and the profile opened "gutted" (connection details
live in separate files and survived). Verified end-to-end with affected
profile data and covered by a synthetic regression test.

Both new functional tests fail on pre-fix code
(`PackageSelfUninstallTest` trips ASan heap-use-after-free;
`ProfileLoadTempFileTest` reproduces the data loss) and pass with the
fix; full functional suite green (24/24).

Known remaining (pre-existing) issue documented in-code at
`Host::pendingXmlSaveFutures()`: module writing still touches `writers`
from the background task for profiles that use modules; fixing that
properly means moving module serialization back to the main thread and
deserves its own PR.

**Test case:**
1. Create a package containing a timer or event-handler script that
calls `uninstallPackage()` on its own package, and let it fire - no
crash, package cleanly removed, next save does not resurrect it.
2. Simulate an interrupted save: place an empty file named like
`2026-01-01#12-00-00.xml.AbCdEf` in a profile's `current/` folder with
the newest timestamp - the profile still loads the newest real save with
all triggers intact, and the temporary no longer appears in Connect →
Options → Profile history.
3. `ctest -R "ProfileLoadTempFileTest|PackageSelfUninstallTest"` in an
ASan (default Debug) build.


Assisted-by: Claude:claude-fable-5
Assisted-by: Claude:claude-opus-4-8
Signed-off-by: Vadim Peretokin <vadim.peretokin@mudlet.org>
2026-07-30 10:49:10 +02:00
Vadim Peretokin
1a41a2235a
improve: split the games list into My games and All games tabs (#9452)
#### Brief overview of PR changes/additions
- Tab bar above the connection dialog's games list: "My games" (profiles
on disk) and "All games" (full catalog), replacing the right-click "Show
my profiles only" stop-gap
- Opens on My games when profiles exist, All games on a fresh install;
last-used tab and the retired filter's setting are honoured; selection
preserved across tab switches
- Dedicated single-game builds hide the tab bar and keep their current
look

#### Motivation for adding to Mudlet
One flat grid of ~35 games overwhelmed newcomers and buried the profiles
a player actually uses.

#### Other info (issues closed, discussion etc)
Closes #9140

**Test case:** Fresh install → dialog opens on All games with the tab
bar visible. Create a profile, restart → opens on My games showing only
that profile, selected with details filled. Switch tabs → selection
follows where possible; right-click menu no longer offers "Show my
profiles only".



https://github.com/user-attachments/assets/50b07d6e-7908-4d25-8b4c-4c6e1396aaea

---------

Signed-off-by: Vadim Peretokin <vadim.peretokin@mudlet.org>
2026-07-29 17:14:14 +02:00
Vadim Peretokin
cb402cc3e6
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
Vadim Peretokin
b670b4de61
fix: show connection details when picking a game on a fresh install (#9451)
#### Brief overview of PR changes/additions
- Selecting a game on a first-ever launch now swaps the welcome message
for the connection details and restores the dialog height
- Programmatic selections (startup pre-selection, password-migration
refreshes) still show the welcome message
- Copying a pre-configured game now rebuilds the list and selects the
copy with its details filled in

#### Motivation for adding to Mudlet
On a fresh install, clicking a game filled hidden fields and appeared to
do nothing - a dead end for the very first thing a new user does.

#### Other info (issues closed, discussion etc)
Closes #3930

**Test case:** Fresh install (empty config dir) → connection dialog
shows the welcome message → click any game → connection details appear
filled in. Copy a pre-configured game → the copy appears in the list,
selected, with editable details.



https://github.com/user-attachments/assets/de6df8fa-e1f0-4564-91e8-c286bebdd791

Signed-off-by: Vadim Peretokin <vadim.peretokin@mudlet.org>
2026-07-22 05:02:00 +02:00
Vadim Peretokin
b23d6f788e
Infrastructure: fix else-after-return in codebase (#9096)
<!-- Keep the title short & concise so anyone non-technical can
understand it,
     the title appears in PTB changelogs -->
#### Brief overview of PR changes/additions
Remove unnecessary else/else-if after return, break, continue, and throw
statements - and in places where fixing them is more trouble than its
worth, added NOLINT.
#### Motivation for adding to Mudlet

https://clang.llvm.org/extra/clang-tidy/checks/readability/else-after-return.html,
so it doesn't pop up in PR reviews.
#### Other info (issues closed, discussion etc)

---------

Co-authored-by: Stephen Lyons <slysven@virginmedia.com>
Co-authored-by: Vadim Peretokin <vadi2@users.noreply.github.com>
Co-authored-by: Claude <noreply@anthropic.com>
2026-07-18 18:39:57 +02:00
Vadim Peretokin
f49512066f
add: Show tutorial invitation on first launch (#9169)
#### Brief overview of PR changes/additions
- On first launch (no existing profiles), show a focused tutorial
invitation instead of the full games list
- "Start Tutorial" link launches the Mudlet Tutorial profile directly
- "Skip - show me the games list" button reveals the full connection
dialog with the tutorial profile preselected
- Make the Connect button the dialog's explicit default so Enter in the
connection form always means "Connect" (see below)

#### Motivation for adding to Mudlet
New users currently face a wall of 20+ unknown game names with no
guidance - a focused tutorial invitation improves first-time retention
by teaching basics before choosing a game.


#### Other info (issues closed, discussion etc)


https://github.com/user-attachments/assets/2eeb3cb9-f596-4744-b187-24e1fc785246



---
Squash-merge trailers:

```
Assisted-by: Claude:claude-fable-5
Signed-off-by: Vadim Peretokin <vadim.peretokin@mudlet.org>
```

---------

Co-authored-by: Vadim Peretokin <vadi2@users.noreply.github.com>
Co-authored-by: Vadim Peretokin <vadim.peretokin@mudlet.org>
2026-07-18 10:54:52 +02:00
Manjeet Pathak
b1b0623377
fix: copying of default profiles after fresh install (#9317)
## fix: copying default and welcome profiles now produces a working copy

### Brief overview of PR changes/additions
- Makes the **Copy** and **Copy settings only** buttons work for default
and welcome profiles.
- When the source profile has no folder on disk, writes its displayed
connection data (URL, port, SSL, login, website, description) into the
new profile so the copy is functional and persists.
- Improves the first-run user experience for copying profiles.

### Motivation for adding to Mudlet
Previously, copying a default or welcome profile appeared to succeed but
produced a broken profile: the connect button stayed disabled and the
copy vanished once the connection screen was reopened. Default profiles
only exist in memory until connected to or saved, so there was no folder
for the copy operation to copy from. This makes copying actually work
and improves the first-run experience.

### Other info (issues closed, discussion etc)
- Fixes "Copy" button does not work properly immediately after fresh
install (#9247).
- Supersedes #9256; per the consensus there, this makes copying work for
default profiles instead of disabling the button.
- **Root cause:** default profiles are generated dynamically and are not
available as on-disk profiles for copy operations.
- No new tests added yet; changes are limited to the copy action
handling and profile data writing.
2026-07-03 08:32:11 +02:00
Nick Shearer
cea7b868ec
improve: improve memory safety by using smart pointers (#9239)
### Refactor: replace raw pointer ownership with smart pointers across
core subsystems

  #### Brief overview of PR changes/additions
Replaces raw pointer ownership patterns with `std::unique_ptr` and
`std::map`
  across several core subsystems:

- **Host**: `mStopWatchMap` (`QMap<int, stopWatch*>` → `std::map<int,
unique_ptr<stopWatch>>`), `profileShortcuts` (`QMap<QString,
QKeySequence*>` → `std::map<QString, unique_ptr<QKeySequence>>`).
Removes `qDeleteAll` in destructor and `delete mMMCPServer`.
  - **TMap**: `mpRoomDB` raw pointer → `unique_ptr`
  - **VarUnit**: `base` raw pointer → `unique_ptr`
- **TTrigger**: condition map storage converted to `unique_ptr`,
destructor simplified
- **discord**: handler and presence maps converted from raw pointer
`QMap` to `unique_ptr` + `std::map`
  - **Updater**: `mFeed` and `mUpdateDialog` converted to `unique_ptr`

  #### Motivation for adding to Mudlet
These patterns were identified as sources of memory leaks and potential
use-after-free bugs. Using smart pointers makes ownership explicit,
eliminates manual cleanup code, and ensures correct destruction even on
early-exit paths.

#### Other info (issues closed, discussion etc)

sorry this one is still pretty big, but most of the changes are the same
for each thing so reviewing them together probably makes sense. sadly
there isn't much to see here other than no slow uptick of heap size :-[

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-05 06:42:09 +02:00
Stephen Lyons
113218930f
Infrastructure: Swap out QtConcurrent module header for sub-module ones (#9246)
#### Brief overview of PR changes/additions
The Qt documentation for `QtConcurrent` points out:
> If you include the `<QtConcurrent>` header, the entire Qt Concurrent
module with the entire Qt Core module will be included, which may
increase compilation times and binary sizes. To use individual functions
from the QtConcurrent namespace, you can include more specific headers.
>
> The table below lists the functions in the QtConcurrent namespace and
their corresponding headers:

|Function|Header|
|--------|------|
|`QtConcurrent::run()`|`<QtConcurrentRun>`|
|`QtConcurrent::task()`| `<QtConcurrentTask>`|

|`QtConcurrent::filter()`,<br>`QtConcurrent::filtered()`,<br>`QtConcurrent::filteredReduced()`|`<QtConcurrentFilter>`|
|`QtConcurrent::map()`,<br>`QtConcurrent::mapped()`,<br>`QtConcurrent::mappedReduced()`|`<QtConcurrentMap>`|

#### Motivation for adding to Mudlet
To speed up the build a little by removing stuff that isn't needed.

#### Other info (issues closed, discussion etc)
In doing this I happened to start cleaning up a couple of header files
`T2DMap.h` and then `mudlet.h`, I then got into converting some
`#include`s into forward declarations in a "include-what-you-use" move.
This then rippled through into a (more than 10!) number of files but
should "improve" things.

Note that the ordering of `#include` in many files seems to be rather
haphazard and is due for some serious overhaul - I suggest that we
should actually declare an "official" style for this project so that
everyone knows what it is.

**During the CI/CB process I discovered that Linux and then MacOS builds
were failing because the file referred to by the `#include
<QtConcurrentTask>` header file was missing, yet was present on my local
PC when I was using the Qt framework from the On-line installer.
Initially I suspected a Debian (and then Devuan - as the packaged
version on my own machine also had this defect AND Ubuntu) package
problem; however it now seems to be an upstream Qt issue as the various
Qt versions & OS combinations suggest that Qt themselves fixed it for Qt
6.10:**
| OS     | QtVersion             | Missing header |
|--------|-----------------------|----------------|
| Windows| 6.11.0 package        |       No       |
| Devuan | 6.8.2 package         |      Yes       |
| Devuan | 6.10.0 online install |       No       |
| Ubuntu | 6.9.0 package         |      Yes       |
| Debian | 6.8.2 package         |      Yes       |
| MacOS  | 6.9.0 package         |      Yes       |

**To fix this I reverted to an `#include <qtconcurrenttask.h>` for Linux
and MacOS builds - although it would probably have been better to make
it conditional on the Qt Version instead...**

*I have reported this upstream to Debian - see:
https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=1135197*

---------

Signed-off-by: Stephen Lyons <slysven@virginmedia.com>
2026-04-29 13:35:29 +01:00
Stephen Lyons
aefa87c684
Fix: Clazy warnings part 7 - isempty-vs-count (#9235)
#### Brief overview of PR changes/additions
One of a series of PRs, each addressing a type of issue reported by
Clazy.

This is the: "Use isEmpty() instead [-Wclazy-isempty-vs-count]" one.

#### Motivation for adding to Mudlet
Remove warnings detected by the Clazy tool - either when explicitly run
on the Mudlet code-base or detected by the background scanner/analyser
that Qt Creator offers.

#### Other info (issues closed, discussion etc)
A summary I found for this is:
> Using isEmpty() is preferred over comparing count to zero because it
is more efficient and improves code readability. This is especially
important for collections that do not implement
`RandomAccessCollection`, as counting elements can be costly.

However a conflicting alternative merely suggests that only the
semantics are better reflect by the use of `isEmpty` - there is no
difference in performance - and the first bit is certainly the basis of
our (Mudlet Core Devs) existing preference for it.

Signed-off-by: Stephen Lyons <slysven@virginmedia.com>
2026-04-26 20:05:35 +01:00
Stephen Lyons
8b0e62b334
Fix: Clazy warnings part 1 - range-loop-detach (#9195)
#### Brief overview of PR changes/additions
One of a series of PRs, each addressing a type of issue reported by
Clazy.

This is the: "C+11 range-loop might detach Qt container
[clazy-range-loop-detach]" one.

#### Motivation for adding to Mudlet
Remove warnings detected by the Clazy tool - either when explicitly run
on the Mudlet code-base or detected by the background scanner/analyser
that Qt Creator offers.

#### Other info (issues closed, discussion etc)
There was over 100 of this particular item - some of them also could
have `const` applied to the iterator.

---------

Signed-off-by: Stephen Lyons <slysven@virginmedia.com>
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
2026-04-16 14:30:33 +01:00
Vadim Peretokin
5566bbd57e
Add: right-click option to hide default profiles in connection dialog (#9123)
<!-- Keep the title short & concise so anyone non-technical can
understand it,
     the title appears in PTB changelogs -->
#### Brief overview of PR changes/additions
Inspired by https://github.com/Mudlet/Mudlet/pull/9057 and all the
discussion surrounding it, add an option to hide default profiles
without deleting them entirely.
#### Motivation for adding to Mudlet
Close https://github.com/Mudlet/Mudlet/issues/8474
#### Other info (issues closed, discussion etc)
This is a stop-gap solution before we get redesigned connection screen,
as adding more and more options to a busy dialog is making it more
difficult for first-time players to get started with Mudlet.
2026-04-16 12:56:10 +02:00
Morquin
3036e1f3d7
improve: Give players full control over Discord Rich Presence (#9116)
## Summary

Players had no clear way to control what Discord shows about their
Mudlet activity. The old checkbox in the connection pane only gated
server GMCP data but didn't prevent Discord from showing "Playing
Mudlet", and the privacy controls were confusing. This PR replaces all
of that with three straightforward modes via radio buttons in Profile
Preferences > Chat:

- **Show full game details (if supported)** - full game integration with
server-provided presence (default)
- **Show Mudlet only** - only shows "playing Mudlet", game server is not
told about Discord
- **Disabled** - Discord shows nothing about Mudlet

Players pick the mode that matches their comfort level, and the existing
privacy checkboxes (hide detail, hide state, etc.) remain available in
Game details mode for finer control.

### What changed

- **Three-mode radio buttons** in Profile Preferences > Chat with a
two-column layout (modes on the left, privacy controls on the right),
replacing the old connection-pane checkbox
- **Server-origin tracking** so privacy checkboxes only gate data sent
by the game server - Lua API calls always pass through (only Disabled
mode blocks Lua entirely)
- **Mid-session mode switching** via dynamic GMCP negotiation
(Core.Supports.Add/Remove + External.Discord.Hello/Get)
- **Deferred RPC init** - Discord RPC now starts when a profile loads,
not on app launch
- **Username restriction improvements** - takes effect immediately,
case-insensitive (Discord usernames are lowercase-only since 2023),
shuts down RPC when mismatched
- **Shows logged-in Discord user** in preferences next to the
restriction field, with a tooltip explaining the desktop app requirement
when not connected
- **Presence fix** - empty string fields now send nullptr so Discord
hides them instead of showing blanks
- **Memory leak fix** - presence allocations are now freed in the
destructor regardless of RPC state

### Cleanup

- Removed obsolete discriminator field
(`mRequiredDiscordUserDiscriminator`) - Discord removed discriminators
in 2023
- Removed dead code (`getDiscordUserDetails()`, never called)
- Restored `Discord_ClearPresence` function pointer for potential future
use
- Use proper `Host::DiscordOptionFlags` types instead of raw `int`
(thanks @SlySven)

### Known quirks

- The "Hide timer" checkbox correctly omits timestamps from presence
data, but Discord's client starts its own activity timer for any
presence without a timestamp - this is Discord client behavior outside
our control.
- The "Hide large icon" setting clears the image key, but some Discord
clients fall back to the application's default icon instead of hiding it
entirely.

### Test plan

- [ ] Open Profile Preferences > Chat tab
- [ ] Switch between the three radio button modes and verify Discord
presence updates accordingly
- [ ] In Game details mode, toggle privacy checkboxes and verify fields
are hidden/shown
- [ ] Use Lua API (e.g. `setDiscordDetail("test")`) in Mudlet only mode
- should work. Try in Disabled mode - should fail with error
- [ ] Set a username restriction and verify presence clears immediately
if mismatched
- [ ] Run unit tests: `cd build && ./test/DiscordTest`
- [ ] Run functional tests: `cd build && ctest -R TDiscordModeTest -V`

Closes #6967. Supersedes #7438.

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: SlySven <slysven@virginmedia.com>
2026-04-08 13:33:09 +02:00
Excellencedev
30e6531b7d
Add telnet:// URI scheme support for one-click MUD connections (#8601)
<!-- Keep the title short & concise so anyone non-technical can
understand it,
     the title appears in PTB changelogs -->
#### Brief overview of PR changes/additions
## Summary

Implements full support for `telnet://` links in Mudlet, allowing users
to connect to MUD servers with a single click from browsers or other
applications.


## Usage Example

Users can now click links like:
```html
<a href="telnet://aardmud.org:4000">Play Aardwolf MUD</a>
<a href="telnet://batmud.bat.org">Connect to BatMUD</a>
```

Or run from command line:
```bash
mudlet "telnet://testserver.com:2000"
```

Mudlet will:
1. Parse the URI
2. Find or create a matching profile
3. Auto-connect to the MUD server



### TESTING
I ran it on WEB and it worked fine. I ran it from command line, it
worked fine

HERE IS A DEMO VIDEO :
https://github.com/user-attachments/assets/143cc98a-3453-4bd9-bbe8-5b48c58db490


#### Motivation for adding to Mudlet
The bounty and to improve myself and contribute

#### Other info (issues closed, discussion etc)
/closes #689 
/claim #689 


#### CHECKLIST:
[x] Windows
[x] Linux
[x] macOS

---------

Co-authored-by: Stephen Lyons <slysven@virginmedia.com>
Co-authored-by: Mike Conley <sousesider@gmail.com>
2026-04-04 08:35:02 +02:00
Vadim Peretokin
2cf23b8664
fix: Fix QFutureWatcher memory leaks across the codebase (#9095)
#### Brief overview of PR changes/additions
A few places allocate a QFutureWatcher with new but don't clean it up
afterwards. Added deleteLater() to all of them: Host::saveProfile,
dlgConnectionProfiles::slot_copyProfile, dlgProfilePreferences theme
download handler, TLuaInterpreter::unzipAsync, and Updater (3 sites).

#### Motivation for adding to Mudlet
Small housekeeping fix. Each leaked watcher is ~230 bytes so the
practical impact is minimal, but it's good practice to clean up after
ourselves. The Host::saveProfile one is the most frequent since autosave
runs every 2 minutes.

#### Other info (issues closed, discussion etc)
Test case: review the one-line diffs, confirm deleteLater() is called
after the watcher's finished signal work is done.
2026-03-22 12:09:37 +01:00
Mike Conley
31131ad191
Fix: Changing one profile's password no longer affects others (#9049)
#### Brief overview of PR changes/additions

Fixes an issue where changing the password for one profile would
overwrite the passwords of other profiles. This primarily affected
Windows users due to how the system credential storage works differently
from macOS and Linux.

#### Motivation for adding to Mudlet

Users reported that setting a password on one profile caused all other
profiles to use that same password. This made it impossible to have
different passwords for different game connections. The fix ensures each
profile's password is stored separately on all platforms.

#### Other info (issues closed, discussion etc)

Closes #9006
2026-03-18 17:15:40 +01:00
Mike Conley
fa61c537a7
Fix: Restore password loading from portable file in connection dialog (#8993)
#### Brief overview of PR changes/additions

Restores the ability for the connection dialog to load saved passwords
from the portable password file on disk, which stopped working after PR
#7956 introduced secure credential management.

#### Motivation for adding to Mudlet

After #7956, users who had passwords saved in the old portable file
format (`~/.config/mudlet/profiles/<name>/password`) could no longer see
their passwords pre-filled when selecting a profile in the connection
dialog. This affected users who had not yet migrated to the system
keychain.

The fix ensures all three password sources are checked in order: system
keychain, QSettings, and finally the portable file on disk. It also
moves the password migration step to run before the connection dialog
opens, so passwords are migrated in time to be found.

Includes the profile password collision fix from #8948 and adds new unit
tests for the password migration and fallback paths.

#### Other info (issues closed, discussion etc)

Reported by @SlySven — PR #7956 (commit 79ab7be4) broke password loading
for profiles that only had passwords in the portable file.

---------

Co-authored-by: Stephen Lyons <slysven@virginmedia.com>
2026-03-08 14:38:15 +01:00
Mike Conley
eabdf1b7e7
Fix: Crash when removing default MUD profiles from welcome window (#8916)
#### Brief overview of PR changes/additions

Fixes a crash that occurred when users tried to remove the default MUD
profiles from the welcome window on Linux.

#### Motivation for adding to Mudlet

Users on Linux were experiencing crashes when clicking the remove button
on the default game profiles in the connection dialog. This made it
difficult to clean up the profile list.

#### Other info (issues closed, discussion etc)

Closes #8907

The fix ensures that password cleanup operations happen one at a time
instead of simultaneously, and adds safety checks to handle cases where
the dialog might close during the cleanup process.
2026-02-09 11:44:01 +01:00
Stephen Lyons
e9bfc5ae63
Fix: clean up comments and related translation things (#8914)
#### Brief overview of PR changes/additions
Removes the `*`s in all but the first line of a multi-line translation
comment that explains to the translators details of the Engineering
English text in the source code. Whilst beginning each new line with an
`*` can happen automagically in the Qt Creator as a result of the
"Enable Doxygen Blocks" and "Add leading asterisks" options (in
"Preferences" -> "Text Editor" -> "Documentation Comments") these are
not always stripped out by the `lupdate` utility that generates the
`mudlet.ts` file.

Removes remaining `""` and replaces with `nullptr` any second arguments
to `QObject::tr(...)` where a third argument is needed for the quantity
for "numerus" (quantity dependent) translatable texts.

Removes some, now obsolete, Windows specific code that identified if a
32-Bit version of Mudlet was being run in a 64-Bit Operating System.
Since we no longer can produce such 32-Bit code it is now just cruft.

Revises some quantity dependent code in `./src/dlgPackManager.cpp` so
that only the zero cases are handled differently - in one place this
means the conversion of a `.length() > 0` to an inverted `.isEmpty()` -
and in three other places where a zero or one case was handled
differently to the more than one case.

#### Motivation for adding to Mudlet
Improve the code quality and/or the situation for our translators.

#### Other info (issues closed, discussion etc)

Signed-off-by: Stephen Lyons <slysven@virginmedia.com>
2026-02-07 15:26:09 +00:00
Vadim Peretokin
b6738dd8c2
infrastructure: Apply clang-format to all CPP files (#8804)
#### Brief overview of PR changes/additions
Ran clang-format on all 134 CPP files in src/ using the project's
.clang-format config

#### Motivation for adding to Mudlet
Ensures consistent code formatting across the codebase.

#### Other info (issues closed, discussion etc)
None

**Test case:** Build the project and verify it compiles successfully.
2026-01-19 18:10:44 +01:00
Vadim Peretokin
e7f22e5dbc
Infrastructure: reduce Mudlet build times by 30s (#8403)
<!-- Keep the title short & concise so anyone non-technical can
understand it,
     the title appears in PTB changelogs -->
#### Brief overview of PR changes/additions
Reduce Mudlet build times
#### Motivation for adding to Mudlet
Address #6765, and give developers a better experience.
#### Other info (issues closed, discussion etc)
  Benchmark Results

=== Testing: development (before) (development) ===
Run 1 of 3...
  Time: 226.071397436s
Run 2 of 3...
  Time: 216.059263209s
Run 3 of 3...
  Time: 221.592760908s

=== Testing: PR #8403 (after) (pr-8403) ===
Run 1 of 3...
  Time: 191.112400957s
Run 2 of 3...
  Time: 193.975717783s
Run 3 of 3...
  Time: 196.569316252s


[benchmark-mudlet-build.sh](https://github.com/user-attachments/files/24466717/benchmark-mudlet-build.sh)

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Vadim Peretokin <vadi2@users.noreply.github.com>
2026-01-08 21:04:24 +01:00
Vadim Peretokin
2cc10e8db6
Fix: default new profiles to UTF-8 encoding instead of ASCII (#8759)
#### Brief overview of PR changes/additions

  New profiles now correctly default to UTF-8 encoding instead of ASCII.

  #### Motivation for adding to Mudlet

New profiles were unintentionally defaulting to ASCII because empty
profile data was treated as "ASCII" rather than "not yet set".

  #### Other info (issues closed, discussion etc)

  **Test case:**
  1. Create a new profile
  2. Go to Settings → General → Game protocols
  3. Encoding should show UTF-8, not ASCII
2026-01-07 18:45:19 +01:00
Mike Conley
8579baa69e
Fix: Test builds now use keychain for password storage (#8589)
#### Brief overview of PR changes/additions

Simplifies test environment detection to only check for the
`MUDLET_TEST_MODE` environment variable. Also improves error messages to
be clearer when no password has been saved yet.

#### Motivation for adding to Mudlet

Users manually testing PR builds (like `Mudlet-Test-PR-8369.AppImage`)
were unintentionally bypassed from using the system keychain because
"Test" appeared in the executable name. This caused confusion during
testing and made password storage behave differently than in release
builds.

#### Other info (issues closed, discussion etc)

Related to PR #8369 testing feedback. CI automated tests will continue
to work by setting `MUDLET_TEST_MODE=1` environment variable.
2025-11-27 08:23:34 +01:00
Vadim Peretokin
745c4a5b59
Infrastructure: Remove else-after-return anti-pattern (#8575)
<!-- Keep the title short & concise so anyone non-technical can
understand it,
     the title appears in PTB changelogs -->
#### Brief overview of PR changes/additions
Remove unnecessary else blocks following return statements throughout
the codebase.
#### Motivation for adding to Mudlet
Better code readability 
#### Other info (issues closed, discussion etc)
Other anti patterns are visible in this PR thanks to this, but let's
keep this PR focused on one pattern only.

---------

Co-authored-by: Claude <noreply@anthropic.com>
2025-11-25 18:10:28 +01:00
Stephen Lyons
24d880914b
Add: Happy-eyeballs (try IPv4 and IPv6 connections simultaneously) (#8135)
#### Brief overview of PR changes/additions
Overhauls the profile connection process to identify whether IPv4 and/or
IPv6 protocol addresses are available for the entered details and if
BOTH are to then try to connect with both at the same time (with a
slight preference for IPv6 {by starting it first}). The first one to
connect (including securely and/or via a proxy) wins out and terminates
the process using the other address protocol. This technique is known as
[Happy Eyeballs](https://en.wikipedia.org/wiki/Happy_Eyeballs).

#### Motivation for adding to Mudlet
To improve the end-user's experience if they are fortunate to be on a
"dual-stack" (both protocols available) network.

#### Other info (issues closed, discussion etc)
The on-screen messages have been massaged somewhat to inform the user
about the nature of the connection(s) being made. ALL the addresses that
have been found for the Server are listed instead of just the first one
{this includes a "numerus" translatable string which will need
processing by yours truly!} Should a raw IP address be provided instead
of a "URL" then the result of the "reverse" lookup (trying to get a host
name from the IP address) is displayed instead of just repeating the
address - if anything *is* found - if not that is also noted instead.

One other effect of Mudlet being given a raw IP address is that those
are not normally included in an SSL/TLS certificate - so rendering a
secure connection impossible in that case - this was already noted for
an IPv4 address but NOT for an IPv6 one. This PR corrects that to also
reject secure connections being attempted if an IPv6 address is provided
as the URL.

---------

Signed-off-by: Stephen Lyons <slysven@virginmedia.com>
2025-11-10 23:34:39 +00:00
Kebap
33814ddefb
Fix: Unshrink list of profile history (#8313)
#### Brief overview of PR changes/additions

Increase the width _of the list popup items_ in connection window to
allow for long texts in profile history.

#### Motivation for adding to Mudlet
Better UI.

<img width="794" height="437" alt="grafik"
src="https://github.com/user-attachments/assets/170ddbb1-72c6-4773-b6e5-3cfaa77527ce"
/>


#### Other info (issues closed, discussion etc)

Fix #8300 
Alternative to #8308
2025-11-03 11:09:42 +01:00
Vadim Peretokin
8cb58cf0ca
Infrastructure: remove MSVC leak detection (#8378)
<!-- Keep the title short & concise so anyone non-technical can
understand it,
     the title appears in PTB changelogs -->
#### Brief overview of PR changes/additions
Remove support for leak detection using MSVC - this functionality hasn't
been in use in a decade, and we're switching to to using
[LeakSanitizer](https://github.com/google/sanitizers/wiki/AddressSanitizerLeakSanitizer)
instead.
#### Motivation for adding to Mudlet
Cleaner code.
#### Other info (issues closed, discussion etc)

---------

Co-authored-by: Claude <noreply@anthropic.com>
2025-10-21 15:11:09 +01:00
Mike Conley
f7ac80cfa4
Fix: Password not persisting in connection dialog (#8367)
#### Brief overview of PR changes/additions

Fixed a bug where passwords entered in the Options tab of the connection
dialog were not being saved correctly to the system keychain. The issue
was caused by a service name mismatch - passwords were stored with one
identifier but retrieved using a different one, causing them to never be
found.

Changed `writeSecurePassword()` and `deleteSecurePassword()` in
`dlgConnectionProfiles.cpp` to use the correct wrapper methods
(`storePassword()` and `removePassword()`) instead of calling low-level
credential functions directly.

#### Motivation for adding to Mudlet

Users were experiencing frustration with having to re-enter their
passwords every time they opened a profile or restarted Mudlet. This fix
ensures passwords persist correctly across sessions when secure storage
is enabled, improving the user experience.

#### Other info (issues closed, discussion etc)

The bug only affected the version 4.20+ password storage scheme. The fix
ensures consistent service name formatting
(`Mudlet-{ProfileName}-character`) for both storage and retrieval
operations.
2025-10-21 10:01:35 +01:00
Vadim Peretokin
85a3e01dd7
Fix: 1440 byte leak fix in connection profiles (#8318)
<!-- Keep the title short & concise so anyone non-technical can
understand it,
     the title appears in PTB changelogs -->
#### Brief overview of PR changes/additions
Fix: 1440 byte leak fix in connection profiles
#### Motivation for adding to Mudlet
Don't leak memory, not even a little bit
#### Other info (issues closed, discussion etc)
2025-10-12 10:20:24 +02:00
Mike Conley
3b2874db04
Fix: prevent password loss in PTBs and improve credential debug messages (#8257)
#### Brief overview of PR changes/additions

- Fixed a bug where users lost their saved passwords when switching
between different Mudlet versions
- Added safety checks to prevent password deletion unless you're using a
newer version (4.20.0+)
- Made error messages clearer and more helpful when password loading
fails
- Improved the system's ability to find passwords stored in different
locations
- Added better logging to help diagnose password-related issues

#### Motivation for adding to Mudlet

Users reported that their saved passwords disappeared after trying
development versions of Mudlet. This happened because the system was
automatically cleaning up old password storage without considering that
users might switch back to older versions. The unclear error messages
also made it difficult to understand what was happening when passwords
couldn't be loaded.

#### Other info (issues closed, discussion etc)

This fix ensures users can safely test different Mudlet versions without
losing their saved passwords. It also makes password-related problems
easier to understand and troubleshoot, while maintaining compatibility
with all existing Mudlet installations.

---------

Co-authored-by: Vadim Peretokin <vadi2@users.noreply.github.com>
2025-09-24 13:28:15 +02:00
Zooka
ff78c9a696
Improve widget naming in connection profile window (#8217)
<!-- Keep the title short & concise so anyone non-technical can
understand it,
     the title appears in PTB changelogs -->
#### Brief overview of PR changes/additions
Improve widget naming in connection profile window for developers so
they are more specific.

#### Motivation for adding to Mudlet
Happy devs.

#### Other info (issues closed, discussion etc)
closes #6039
2025-09-19 13:12:41 +07:00
Kebap
3d88bc3bd7
Infra: Use modern For loop syntax (#8162)
#### Brief overview of PR changes/additions
No functional change, just internal refactoring

#### Motivation for adding to Mudlet
Easier to read, clearer style

#### Other info (issues closed, discussion etc)
2025-09-16 11:15:54 +00:00
Zooka
2d0aaea719
Add a Mudlet Tutorial profile. (#8188)
<!-- Keep the title short & concise so anyone non-technical can
understand it,
     the title appears in PTB changelogs -->
#### Brief overview of PR changes/additions
Adds a Mudlet Tutorial profile which incorporates a basic GUI tutorial
and mini-mud quest.

GUI tutorial introduces the main window, command line, search bar,
toolbar, map.

Mini-quest attempts to act like a MUD. Teaches directional commands and
some typical verbs (look, buy, give, get, open, close, quit) while the
player wanders around trying to figure out the two 'quests' (buy the
sheriff some lunch and find the librarian's lost ring). It is simplistic
and more designed to show players how a mud 'feels and progresses' when
playing rather than a full blown MUD school which most (all?) MUDs
incorporate.

#### Motivation for adding to Mudlet
Help on-boarding new users.  Destination 5.0.

/claim #3072 
helps #2823

#### Other info (issues closed, discussion etc)
I purposefully left out triggers, scripting and all that stuff veteran
players take for granted! This is supposed to be an on-boarding tutorial
for NEW players, let's not overwhelm them. A link after successful
completion of the game introduces the wiki Introduction page for more
advanced topics. That said, any improvements or basic features you think
are missing from either the GUI or Mud tutorials, let me know and I can
add.


https://github.com/user-attachments/assets/1d16db50-e24b-4e78-b6b0-66e0e98a0772
2025-09-13 09:17:13 +02:00
Mike Conley
6a3392b6a1
Improve: Clean up keychain entries when deleting profiles and creating new ones (#8103)
#### Brief overview of PR changes/additions

Adds keychain cleanup functionality to connection profile management:

- **Profile deletion cleanup**: When deleting profiles via the "Remove"
button, now removes associated keychain entries ("character" and "proxy"
passwords) in addition to removing profile directories
- **Orphaned entry detection**: When creating new profiles, detects and
removes orphaned keychain entries from profiles previously deleted
outside Mudlet interface
- **Async implementation**: Uses existing CredentialManager async API
with proper callback handling and debug logging
- **Helper method**: Refactored profile save logic into
`continueProfileSave()` to support async orphaned entry cleanup

#### Motivation for adding to Mudlet

Prevents accumulation of orphaned credentials in system keychains (macOS
Keychain, Windows Credential Store, Linux Secret Service) by ensuring
complete profile lifecycle management. Previously, keychain entries were
left behind when profiles were deleted, and orphaned entries could
interfere with new profile creation.

#### Other info (issues closed, discussion etc)

Builds upon the secure credential management system from PR #7956.
Follows the same async architecture patterns with non-blocking UI
operations, proper memory management, and callback-based error handling.

Changes are only active when secure password storage is enabled.

---

Video with evidence of the keychain being cleared after a profile was
created, closed, removed, recreated:


https://github.com/user-attachments/assets/a5e3e126-8b49-4871-8445-97b938a7c57e

---

Video with evidence of the keychain being cleared upon re-creating a
profile that has a keychain entry in the past:


https://github.com/user-attachments/assets/15b86307-783d-49a4-87cc-06e28a5674b1
2025-08-28 13:03:30 +02:00
Vadim Peretokin
cb70d1856c
Infrastructure: fix Qt 6.10 compile warnings (#8115)
<!-- Keep the title short & concise so anyone non-technical can
understand it,
     the title appears in PTB changelogs -->
#### Brief overview of PR changes/additions
Fix Qt 6.10 compile warnings
#### Motivation for adding to Mudlet
Cleaner compiles
#### Other info (issues closed, discussion etc)

---------

Co-authored-by: Vadim Peretokin <vadi2@users.noreply.github.com>
2025-08-28 13:02:14 +02:00
Mike Conley
46bbef3de9
Fix: Connection dialog character name save issues for default games (#8101)
#### Brief overview of PR changes/additions

- Enhanced `writeProfileData()` to automatically create profile
directories when they don't exist
- Added proper error checking and logging for character name and
password save operations
- Fixed silent failures that caused intermittent loss of saved character
names

#### Motivation for adding to Mudlet

Character names in the connection dialog do not persist initially for
default game profiles (StickMUD, Achaea, etc.) because their directories
don't exist until first connection. Profile data saves failed silently
when directories were missing, with no indication to users. This
particularly affected new users trying default games, requiring multiple
attempts before character names would persist.

#### Other info (issues closed, discussion etc)

- Resolves character name loss for default game profiles in 4.19.1
- Ensures consistent behavior between default and user-created profiles
- Improves reliability of profile data saving system

---

In 4.19.1 it takes 2+ connections for the character name to stick:


https://github.com/user-attachments/assets/3cd0d87e-54a0-4a24-8abe-260c7471fa82

---

With the fix, this is done right away:


https://github.com/user-attachments/assets/84a6a2dc-70f6-475b-8c06-86601d648521
2025-08-27 09:13:07 +02:00
Mike Conley
adb366a051
Fix: Prevent duplicate connection attempts after disconnect-reconnect sequence (#8038)
#### Brief overview of PR changes/additions

Fixes duplicate connection attempts that occurred when performing a
Reconnect → Disconnect → Connect sequence. The issue caused duplicate
DNS lookups and connection attempts, resulting in unnecessary network
overhead and confusing connection logs.

#### Motivation for adding to Mudlet

Users experienced duplicate connection attempts after disconnecting and
reconnecting to the same profile, which created:
- Unnecessary network traffic from duplicate DNS lookups
- Confusing duplicate connection messages in logs
- Potential timing issues with connection establishment

#### Other info (issues closed, discussion etc)

**Root Cause:** Two separate code paths were both attempting to connect:
1. `mudlet::loadProfile()` called `connectIt()` for existing hosts
2. The reconnect path in `dlgConnectionProfiles::loadProfile()` called
`reconnect()`

**Solution:** Added logic to detect when `mudlet::loadProfile()` has
already handled the connection for existing hosts and skip the duplicate
reconnect call.

**Files Modified:**
- `src/dlgConnectionProfiles.cpp` - Added duplicate connection
prevention logic
- `src/mudlet.cpp` - Minor cleanup of related code

---

**Before (Problem)**


https://github.com/user-attachments/assets/1a71067f-f21d-4eaf-a6b3-728589f97bfa

---

**After (Fixed)**


https://github.com/user-attachments/assets/4b28a51d-7c24-4703-8aab-86ec6209c557
2025-08-18 15:31:03 +02:00
Vadim Peretokin
79b806dd42
Fix: Revert "Improve: remove ambiguous width East Asian option (#8020)" (#8057)
<!-- Keep the title short & concise so anyone non-technical can
understand it,
     the title appears in PTB changelogs -->
#### Brief overview of PR changes/additions
This reverts commit 53bcb3112b, as it was
breaking profile XML loading.
#### Motivation for adding to Mudlet
Fix `development` to load profiles.
#### Other info (issues closed, discussion etc)
We should have automated tests about this!

Co-authored-by: Vadim Peretokin <vadi2@users.noreply.github.com>
2025-08-18 15:30:37 +02:00
Mike Conley
79ab7be4e2
Improve: Secure credential management with system keychain integration and legacy migration (#7956)
#### Brief overview of PR changes/additions

This pull request implements secure credential management for Mudlet
with system keychain integration and encrypted fallback storage:

**Core Components:**

- **CredentialManager**: High-level API for secure credential storage
with QtKeychain integration
- **SecureStringUtils**: Qt-based cryptographic utilities for encrypted
file storage
- **Legacy Migration**: Automatic detection and migration of existing
plaintext passwords

**Key Features:**

- **System Keychain Integration**: Primary storage in macOS Keychain,
Windows Credential Store, and Linux Secret Service via QtKeychain
- **Encrypted File Fallback**: Qt crypto-based AES encryption for
portable mode and keychain unavailability
- **Seamless Migration**: Automatic detection and upgrade of legacy
password storage formats
- **Profile Isolation**: Per-profile encryption keys prevent
cross-profile credential access
- **Portable Mode Support**: Automatic detection and secure file-based
storage for portable installations

#### Motivation for adding to Mudlet

**Security Enhancement:**

- Eliminates plaintext password storage in profile XML files
- Provides industry-standard system keychain integration for credential
security
- Implements authenticated encryption for fallback scenarios

**User Experience:**  

- Zero configuration required - works automatically across all platforms
- Seamless migration from existing plaintext passwords to secure storage
- Native system integration provides familiar credential management
experience

**Future-Proofing:**

- Extensible architecture supports additional credential types (API
keys, tokens, etc.)
- Robust fallback ensures functionality in all deployment scenarios
- Prepares foundation for OAuth and external service integrations

#### Other info (issues closed, discussion etc)

**Security Architecture:**

- **Keychain-First Strategy**: Prefers system keychain with automatic
encrypted file fallback
- **Legacy Format Detection**: Automatically migrates passwords from
development branch keychain format
- **Memory Security**: Secure string clearing and controlled credential
lifecycle management
- **Input Validation**: Path traversal protection and service name
sanitization

**Implementation Highlights:**

- **Async Operations**: Non-blocking keychain operations with timeout
protection
- **Thread Safety**: Event-driven architecture prevents UI blocking and
race conditions
- **Comprehensive Testing**: Full test coverage for encryption,
migration, and fallback scenarios
- **Cross-Platform**: Unified API across Windows, macOS, and Linux with
platform-specific optimizations

**Version Compatibility & Migration:**

- **Mudlet 4.19.x Compatibility**: Preserves existing plaintext password
storage to ensure compatibility when switching between 4.19.x stable and
development builds
- **Mudlet 4.20.x Migration**: Post-4.20.0 release, automatic migration
begins converting plaintext passwords to secure storage
- **Bidirectional Safety**: Users can safely run both 4.19.x and
development versions without losing access to their passwords during the
transition period
- **Legacy Format Support**: Automatically detects and migrates
passwords from the original development branch keychain format
(`service="Mudlet profile"`) to the new secure format

# Credential Management Workflows

## 1. Credential Storage Strategy

```mermaid
flowchart TD
    A[Store Password Request] --> B{Portable Mode?}
    B -->|Yes| C[Encrypt & Store in Profile File]
    B -->|No| D[Store in System Keychain]
    D --> E{Keychain Success?}
    E -->|Yes| F[Remove Encrypted Fallback File]
    E -->|No| G[Fallback to Encrypted File]
    F --> H[Success]
    G --> I{Encryption Success?}
    I -->|Yes| H
    I -->|No| J[Failure]
    C --> I
    
    classDef primary fill:#4CAF50,stroke:#2E7D32,stroke-width:2px,color:#000
    classDef fallback fill:#FF9800,stroke:#E65100,stroke-width:2px,color:#000
    classDef decision fill:#2196F3,stroke:#0D47A1,stroke-width:2px,color:#fff
    classDef result fill:#9C27B0,stroke:#4A148C,stroke-width:2px,color:#fff
    
    class D,F primary
    class C,G fallback
    class B,E,I decision
    class H,J result
```

## 2. Legacy Migration Workflow

```mermaid
flowchart TD
    A[Retrieve Password Request] --> B[Try New Keychain Format]
    B --> C{Password Found?}
    C -->|Yes| D[Return Password]
    C -->|No| E[Check Legacy Keychain Format]
    E --> F{Legacy Found?}
    F -->|Yes| G[Migrate to New Format]
    G --> H[Store in New Format]
    H --> I[Remove Legacy Entry]
    I --> J[Return Migrated Password]
    F -->|No| K[Try Encrypted File]
    K --> L{File Found?}
    L -->|Yes| M[Decrypt & Return]
    L -->|No| N[Return Empty - No Password Stored]
    
    classDef newformat fill:#4CAF50,stroke:#2E7D32,stroke-width:2px,color:#000
    classDef legacy fill:#FF9800,stroke:#E65100,stroke-width:2px,color:#000
    classDef migration fill:#9C27B0,stroke:#4A148C,stroke-width:2px,color:#fff
    classDef fallback fill:#607D8B,stroke:#263238,stroke-width:2px,color:#fff
    classDef result fill:#2196F3,stroke:#0D47A1,stroke-width:2px,color:#fff
    
    class B,H newformat
    class E,I legacy
    class G migration
    class K fallback
    class D,J,M,N result
```

## 3. Cross-Platform Keychain Integration

```mermaid
flowchart TD
    A[QtKeychain Request] --> B{Platform Detection}
    B -->|macOS| C[Access Keychain Services]
    B -->|Windows| D[Access Credential Store]
    B -->|Linux| E[Access Secret Service]
    C --> F[Store/Retrieve Credential]
    D --> F
    E --> F
    F --> G{Operation Success?}
    G -->|Yes| H[Return Result]
    G -->|No| I[Log Error & Fallback]
    I --> J[Use Encrypted File Storage]
    J --> K[AES Encryption with Profile Key]
    K --> L[Store in Profile Directory]
    
    classDef platform fill:#2196F3,stroke:#0D47A1,stroke-width:2px,color:#fff
    classDef keychain fill:#4CAF50,stroke:#2E7D32,stroke-width:2px,color:#000
    classDef fallback fill:#FF9800,stroke:#E65100,stroke-width:2px,color:#000
    classDef crypto fill:#9C27B0,stroke:#4A148C,stroke-width:2px,color:#fff
    
    class C,D,E platform
    class F,H keychain
    class I,J fallback
    class K,L crypto
```

This implementation provides a comprehensive, secure, and user-friendly
credential management system that seamlessly upgrades existing Mudlet
installations while providing robust security for future credential
storage needs.

---------

Co-authored-by: Vadim Peretokin <vperetokin@hey.com>
2025-08-17 12:44:38 +02:00
Vadim Peretokin
93a1f3d269
Fix: ignore non-xml files in profile history (#8035)
<!-- Keep the title short & concise so anyone non-technical can
understand it,
     the title appears in PTB changelogs -->
#### Brief overview of PR changes/additions
Fix: ignore non-xml files in profile history.
#### Motivation for adding to Mudlet
Fix https://github.com/Mudlet/Mudlet/issues/8034
#### Other info (issues closed, discussion etc)

Co-authored-by: Vadim Peretokin <vadi2@users.noreply.github.com>
2025-08-17 08:26:26 +00:00
Vadim Peretokin
53bcb3112b
Improve: remove ambiguous width East Asian option (#8020)
<!-- Keep the title short & concise so anyone non-technical can
understand it,
     the title appears in PTB changelogs -->
#### Brief overview of PR changes/additions
Remove ambiguous width East Asian option - while it made sense in
theory, in practice we have not found need for it, and we haven't found
anyone who uses it either - making it hard to justify to keep it.
#### Motivation for adding to Mudlet
Simplifying options by removing this one:

<img width="780" height="417" alt="image"
src="https://github.com/user-attachments/assets/add59fd5-3c46-44a3-a3fb-fbb5414dfbf6"
/>
 
#### Other info (issues closed, discussion etc)
Should we find users who need this, we can revert this PR and do an
update.
2025-08-17 10:15:51 +02:00
Stephen Lyons
ee1cc2e373
Infrastructure: purge trailing whitespace (#7949)
#### Brief overview of PR changes/additions
Use a ` +$` search-and-replace to remove trailing spaces in `.cpp` and
`.h` source code files to remove unwanted trailing whitespace that has
crept into a number of files.

#### Motivation for adding to Mudlet
To clean up files as `git` notices such white-space and objects in some
circumstances - and such spaces are redundant in the source code.

#### Other info (issues closed, discussion etc)

Signed-off-by: Stephen Lyons <slysven@virginmedia.com>
2025-07-13 14:29:14 +01:00