mudlet/src/dlgConnectionProfiles.cpp

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

2887 lines
124 KiB
C++
Raw Permalink Normal View History

2009-01-24 02:50:22 +01:00
/***************************************************************************
* Copyright (C) 2008-2013 by Heiko Koehn - KoehnHeiko@googlemail.com *
* Copyright (C) 2014 by Ahmed Charles - acharles@outlook.com *
* Copyright (C) 2016-2018, 2020-2023, 2025-2026 by Stephen Lyons *
* - slysven@virginmedia.com *
* Copyright (C) 2025 by Lecker Kebap - Leris@mudlet.org *
2009-01-24 02:50:22 +01:00
* *
* 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. *
* *
2009-01-24 02:50:22 +01:00
* 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. *
***************************************************************************/
2009-01-24 02:50:22 +01:00
#include "dlgConnectionProfiles.h"
#include <pugixml.hpp>
2009-01-24 02:50:22 +01:00
#include "Host.h"
#include "HostManager.h"
#include "LuaInterface.h"
#include "TGameDetails.h"
#include "XMLimport.h"
2017-04-14 00:40:02 -07:00
#include "mudlet.h"
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 06:44:38 -04:00
#include "CredentialManager.h"
#include "SecureStringUtils.h"
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
#include "utils.h"
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
#include <QtConcurrentRun>
2017-04-14 00:40:02 -07:00
#include <QtUiTools>
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
#include <QApplication>
#include <QColorDialog>
2021-02-03 19:59:35 +00:00
#include <QDir>
#include <QFileInfo>
#include <QPointer>
#include <QRandomGenerator>
#include <QSettings>
#include <QSignalBlocker>
#include <QTabBar>
#include <QTime>
#include <chrono>
#include <sstream>
2021-02-03 19:59:35 +00:00
using namespace std::chrono_literals;
// Kept to a sub-set of ASCII because the profile name is also used as a
// directory name on all supported OSes; parentheses are included so that
// folders duplicated by a file manager (e.g. "profile (2)") work as-is:
const QString dlgConnectionProfiles::scmAllowedProfileNameChars = qsl(". _()0123456789-#&aAbBcCdDeEfFgGhHiIjJkKlLmMnNoOpPqQrRsStTuUvVwWxXyYzZ");
// Returns the first character not permitted in a (new) profile name, or a
// null QChar if all of them are acceptable. An embedded U+0000 is
// indistinguishable from the all-clear sentinel, but a QLineEdit never lets
// one through:
QChar dlgConnectionProfiles::firstInvalidProfileNameChar(const QString& name)
{
for (const QChar& c : name) {
if (!scmAllowedProfileNameChars.contains(c)) {
return c;
}
}
return {};
}
// Characters that make a name unusable no matter where it came from:
// utils::sanitizeForPath() silently rewrites them out of any path built from
// the profile name, and CredentialManager::generateFilePath() refuses to
// produce a path at all - so a profile named this way could never store or
// retrieve its password. Mirrors the pattern used there:
const QRegularExpression dlgConnectionProfiles::scmUnusableProfileNameChars{qsl(R"REGEX(\.\.|[/\\<>:"|?*\x00-\x1f])REGEX")};
// Listing what is expected rather than what to watch out for keeps an
// unrecognised file - a stored password, a character name the user typed - on
// the side of asking. ProfileDeletionSafetyTest fails if the connection form
// comes to write anything this does not name:
const QStringList dlgConnectionProfiles::scmConnectionDetailFiles{qsl("url"), qsl("port"), qsl("ssl_tsl"), qsl("description"), qsl("website"), qsl("autologin"), qsl("autoreconnect")};
// A lone "." is made entirely of permitted characters, yet every path built
// from it addresses the profiles directory rather than a profile of its own -
// as does "..", which scmUnusableProfileNameChars already covers:
bool dlgConnectionProfiles::profileNameUsableAsIs(const QString& name)
{
return !name.isEmpty() && name != qsl(".") && !name.contains(scmUnusableProfileNameChars);
}
// Resolved textually rather than with QDir::canonicalPath() so that the answer
// does not depend on the folder existing - a predefined game has none until it
// is saved - and so that a symlinked profile folder still resolves.
QString dlgConnectionProfiles::profileFolderPath(const QString& profilesPath, const QString& profile)
{
// Must precede the parent check below, which QDir::cleanPath() would
// otherwise satisfy by collapsing "../profiles/Foo" straight back in:
if (profile.isEmpty() || profile.contains(QLatin1Char('/')) || profile.contains(QLatin1Char('\\'))) {
return {};
}
const QString profilesDir = QDir::cleanPath(profilesPath);
const QString candidate = QDir::cleanPath(qsl("%1/%2").arg(profilesDir, profile));
if (candidate == profilesDir || QFileInfo(candidate).path() != profilesDir) {
return {};
}
return candidate;
}
Revise: select profiles by typing the name, keep profile icons (#4440) Restore the text display of the profile name. This is so that the keyboard selection of profiles beginning with a particular letter works again. This will close issue #4283 . This also sets it to the minimum size trying to hide it by setting the font size to 1 - however that still show something (and a font size of zero is NOT valid). Manipulating the QPalette of the QListWidget does not work well enough in the presence of Styles or StyleSheets to be a viable means of hiding the text either. Also in the `dlgConnection` class: * refactor out some highly repetitive code that sets up the predefined Mud icons. * remove warnings and adopts a change introduced into Qt 5.15 for the QLabel::pixmap() which now requires a Qt::ReturnByValue argument to prevent an obsolete code warning and to force a QPixmap to be returned by value and not reference... * remove a warning about: QListWidget::setItemSelected(QListWidgetItem*,bool) being deprecated and switches to the recommended: QListWidgetItem::setSelected(bool) Edit: the last item above may have become redundant as further commits amalgamated into this PR may have taken out these lines anyhow! Change the custom icon generator so that there is NOT a dependency on the position in the list for the colours used. This means that the same name will always have the same colours - which will help the user to pick out a particular profile as it will always look the same no matter if other profiles are added or removed. Remove the colour gradient in the custom icon as it is not thought to be useful nowadays. Refactor out the colour icon generator to a single method that is called from more than one place. BugFix: restore odd original way of accessing some QPixmaps This was causing failures in some CI platforms using Qt 5.14. or older. Revise: really frob the custom icon generator This is to try and produce as varied but still readable icons when they are generated entirely on the basis of the MUD name - in a reproducible (though Qt do NOT guarantee repeatable qHash() functionality across Qt versions - so they may not stay *that* constant) manner. Refrob: use a table of fixed colours Using only 15 hues but with 3 different saturation level plus 5 grey-scale one should give enough different icons but not generate them too close to each other for those with normal colour vision... Signed-off-by: Stephen Lyons <slysven@virginmedia.com> Co-authored-by: Kebap <kebap_spam@gmx.net>
2020-12-11 17:53:28 +00:00
dlgConnectionProfiles::dlgConnectionProfiles(QWidget* parent)
: QDialog(parent)
2009-01-24 02:50:22 +01:00
{
setupUi(this);
mDateTimeFormat = mudlet::self()->getUserLocale().dateTimeFormat();
if (mDateTimeFormat.contains(QLatin1Char('t'))) {
// There is a timezone identifier in there - which (apart from perhaps
// the period around DST changes) we don't really need and which takes
// up space:
if (mDateTimeFormat.contains(QLatin1String(" t"))) {
// Deal with the space if the time zone is appended to the end of
// the string:
mDateTimeFormat.remove(QLatin1String(" t"), Qt::CaseSensitive);
} else {
mDateTimeFormat.remove(QLatin1Char('t'), Qt::CaseSensitive);
}
}
QPixmap holdPixmap;
Revise: select profiles by typing the name, keep profile icons (#4440) Restore the text display of the profile name. This is so that the keyboard selection of profiles beginning with a particular letter works again. This will close issue #4283 . This also sets it to the minimum size trying to hide it by setting the font size to 1 - however that still show something (and a font size of zero is NOT valid). Manipulating the QPalette of the QListWidget does not work well enough in the presence of Styles or StyleSheets to be a viable means of hiding the text either. Also in the `dlgConnection` class: * refactor out some highly repetitive code that sets up the predefined Mud icons. * remove warnings and adopts a change introduced into Qt 5.15 for the QLabel::pixmap() which now requires a Qt::ReturnByValue argument to prevent an obsolete code warning and to force a QPixmap to be returned by value and not reference... * remove a warning about: QListWidget::setItemSelected(QListWidgetItem*,bool) being deprecated and switches to the recommended: QListWidgetItem::setSelected(bool) Edit: the last item above may have become redundant as further commits amalgamated into this PR may have taken out these lines anyhow! Change the custom icon generator so that there is NOT a dependency on the position in the list for the colours used. This means that the same name will always have the same colours - which will help the user to pick out a particular profile as it will always look the same no matter if other profiles are added or removed. Remove the colour gradient in the custom icon as it is not thought to be useful nowadays. Refactor out the colour icon generator to a single method that is called from more than one place. BugFix: restore odd original way of accessing some QPixmaps This was causing failures in some CI platforms using Qt 5.14. or older. Revise: really frob the custom icon generator This is to try and produce as varied but still readable icons when they are generated entirely on the basis of the MUD name - in a reproducible (though Qt do NOT guarantee repeatable qHash() functionality across Qt versions - so they may not stay *that* constant) manner. Refrob: use a table of fixed colours Using only 15 hues but with 3 different saturation level plus 5 grey-scale one should give enough different icons but not generate them too close to each other for those with normal colour vision... Signed-off-by: Stephen Lyons <slysven@virginmedia.com> Co-authored-by: Kebap <kebap_spam@gmx.net>
2020-12-11 17:53:28 +00:00
holdPixmap = notificationAreaIconLabelWarning->pixmap(Qt::ReturnByValue);
holdPixmap.setDevicePixelRatio(5.3);
Revise: select profiles by typing the name, keep profile icons (#4440) Restore the text display of the profile name. This is so that the keyboard selection of profiles beginning with a particular letter works again. This will close issue #4283 . This also sets it to the minimum size trying to hide it by setting the font size to 1 - however that still show something (and a font size of zero is NOT valid). Manipulating the QPalette of the QListWidget does not work well enough in the presence of Styles or StyleSheets to be a viable means of hiding the text either. Also in the `dlgConnection` class: * refactor out some highly repetitive code that sets up the predefined Mud icons. * remove warnings and adopts a change introduced into Qt 5.15 for the QLabel::pixmap() which now requires a Qt::ReturnByValue argument to prevent an obsolete code warning and to force a QPixmap to be returned by value and not reference... * remove a warning about: QListWidget::setItemSelected(QListWidgetItem*,bool) being deprecated and switches to the recommended: QListWidgetItem::setSelected(bool) Edit: the last item above may have become redundant as further commits amalgamated into this PR may have taken out these lines anyhow! Change the custom icon generator so that there is NOT a dependency on the position in the list for the colours used. This means that the same name will always have the same colours - which will help the user to pick out a particular profile as it will always look the same no matter if other profiles are added or removed. Remove the colour gradient in the custom icon as it is not thought to be useful nowadays. Refactor out the colour icon generator to a single method that is called from more than one place. BugFix: restore odd original way of accessing some QPixmaps This was causing failures in some CI platforms using Qt 5.14. or older. Revise: really frob the custom icon generator This is to try and produce as varied but still readable icons when they are generated entirely on the basis of the MUD name - in a reproducible (though Qt do NOT guarantee repeatable qHash() functionality across Qt versions - so they may not stay *that* constant) manner. Refrob: use a table of fixed colours Using only 15 hues but with 3 different saturation level plus 5 grey-scale one should give enough different icons but not generate them too close to each other for those with normal colour vision... Signed-off-by: Stephen Lyons <slysven@virginmedia.com> Co-authored-by: Kebap <kebap_spam@gmx.net>
2020-12-11 17:53:28 +00:00
notificationAreaIconLabelWarning->setPixmap(holdPixmap);
Revise: select profiles by typing the name, keep profile icons (#4440) Restore the text display of the profile name. This is so that the keyboard selection of profiles beginning with a particular letter works again. This will close issue #4283 . This also sets it to the minimum size trying to hide it by setting the font size to 1 - however that still show something (and a font size of zero is NOT valid). Manipulating the QPalette of the QListWidget does not work well enough in the presence of Styles or StyleSheets to be a viable means of hiding the text either. Also in the `dlgConnection` class: * refactor out some highly repetitive code that sets up the predefined Mud icons. * remove warnings and adopts a change introduced into Qt 5.15 for the QLabel::pixmap() which now requires a Qt::ReturnByValue argument to prevent an obsolete code warning and to force a QPixmap to be returned by value and not reference... * remove a warning about: QListWidget::setItemSelected(QListWidgetItem*,bool) being deprecated and switches to the recommended: QListWidgetItem::setSelected(bool) Edit: the last item above may have become redundant as further commits amalgamated into this PR may have taken out these lines anyhow! Change the custom icon generator so that there is NOT a dependency on the position in the list for the colours used. This means that the same name will always have the same colours - which will help the user to pick out a particular profile as it will always look the same no matter if other profiles are added or removed. Remove the colour gradient in the custom icon as it is not thought to be useful nowadays. Refactor out the colour icon generator to a single method that is called from more than one place. BugFix: restore odd original way of accessing some QPixmaps This was causing failures in some CI platforms using Qt 5.14. or older. Revise: really frob the custom icon generator This is to try and produce as varied but still readable icons when they are generated entirely on the basis of the MUD name - in a reproducible (though Qt do NOT guarantee repeatable qHash() functionality across Qt versions - so they may not stay *that* constant) manner. Refrob: use a table of fixed colours Using only 15 hues but with 3 different saturation level plus 5 grey-scale one should give enough different icons but not generate them too close to each other for those with normal colour vision... Signed-off-by: Stephen Lyons <slysven@virginmedia.com> Co-authored-by: Kebap <kebap_spam@gmx.net>
2020-12-11 17:53:28 +00:00
holdPixmap = notificationAreaIconLabelError->pixmap(Qt::ReturnByValue);
holdPixmap.setDevicePixelRatio(5.3);
Revise: select profiles by typing the name, keep profile icons (#4440) Restore the text display of the profile name. This is so that the keyboard selection of profiles beginning with a particular letter works again. This will close issue #4283 . This also sets it to the minimum size trying to hide it by setting the font size to 1 - however that still show something (and a font size of zero is NOT valid). Manipulating the QPalette of the QListWidget does not work well enough in the presence of Styles or StyleSheets to be a viable means of hiding the text either. Also in the `dlgConnection` class: * refactor out some highly repetitive code that sets up the predefined Mud icons. * remove warnings and adopts a change introduced into Qt 5.15 for the QLabel::pixmap() which now requires a Qt::ReturnByValue argument to prevent an obsolete code warning and to force a QPixmap to be returned by value and not reference... * remove a warning about: QListWidget::setItemSelected(QListWidgetItem*,bool) being deprecated and switches to the recommended: QListWidgetItem::setSelected(bool) Edit: the last item above may have become redundant as further commits amalgamated into this PR may have taken out these lines anyhow! Change the custom icon generator so that there is NOT a dependency on the position in the list for the colours used. This means that the same name will always have the same colours - which will help the user to pick out a particular profile as it will always look the same no matter if other profiles are added or removed. Remove the colour gradient in the custom icon as it is not thought to be useful nowadays. Refactor out the colour icon generator to a single method that is called from more than one place. BugFix: restore odd original way of accessing some QPixmaps This was causing failures in some CI platforms using Qt 5.14. or older. Revise: really frob the custom icon generator This is to try and produce as varied but still readable icons when they are generated entirely on the basis of the MUD name - in a reproducible (though Qt do NOT guarantee repeatable qHash() functionality across Qt versions - so they may not stay *that* constant) manner. Refrob: use a table of fixed colours Using only 15 hues but with 3 different saturation level plus 5 grey-scale one should give enough different icons but not generate them too close to each other for those with normal colour vision... Signed-off-by: Stephen Lyons <slysven@virginmedia.com> Co-authored-by: Kebap <kebap_spam@gmx.net>
2020-12-11 17:53:28 +00:00
notificationAreaIconLabelError->setPixmap(holdPixmap);
Revise: select profiles by typing the name, keep profile icons (#4440) Restore the text display of the profile name. This is so that the keyboard selection of profiles beginning with a particular letter works again. This will close issue #4283 . This also sets it to the minimum size trying to hide it by setting the font size to 1 - however that still show something (and a font size of zero is NOT valid). Manipulating the QPalette of the QListWidget does not work well enough in the presence of Styles or StyleSheets to be a viable means of hiding the text either. Also in the `dlgConnection` class: * refactor out some highly repetitive code that sets up the predefined Mud icons. * remove warnings and adopts a change introduced into Qt 5.15 for the QLabel::pixmap() which now requires a Qt::ReturnByValue argument to prevent an obsolete code warning and to force a QPixmap to be returned by value and not reference... * remove a warning about: QListWidget::setItemSelected(QListWidgetItem*,bool) being deprecated and switches to the recommended: QListWidgetItem::setSelected(bool) Edit: the last item above may have become redundant as further commits amalgamated into this PR may have taken out these lines anyhow! Change the custom icon generator so that there is NOT a dependency on the position in the list for the colours used. This means that the same name will always have the same colours - which will help the user to pick out a particular profile as it will always look the same no matter if other profiles are added or removed. Remove the colour gradient in the custom icon as it is not thought to be useful nowadays. Refactor out the colour icon generator to a single method that is called from more than one place. BugFix: restore odd original way of accessing some QPixmaps This was causing failures in some CI platforms using Qt 5.14. or older. Revise: really frob the custom icon generator This is to try and produce as varied but still readable icons when they are generated entirely on the basis of the MUD name - in a reproducible (though Qt do NOT guarantee repeatable qHash() functionality across Qt versions - so they may not stay *that* constant) manner. Refrob: use a table of fixed colours Using only 15 hues but with 3 different saturation level plus 5 grey-scale one should give enough different icons but not generate them too close to each other for those with normal colour vision... Signed-off-by: Stephen Lyons <slysven@virginmedia.com> Co-authored-by: Kebap <kebap_spam@gmx.net>
2020-12-11 17:53:28 +00:00
holdPixmap = notificationAreaIconLabelInformation->pixmap(Qt::ReturnByValue);
holdPixmap.setDevicePixelRatio(5.3);
Revise: select profiles by typing the name, keep profile icons (#4440) Restore the text display of the profile name. This is so that the keyboard selection of profiles beginning with a particular letter works again. This will close issue #4283 . This also sets it to the minimum size trying to hide it by setting the font size to 1 - however that still show something (and a font size of zero is NOT valid). Manipulating the QPalette of the QListWidget does not work well enough in the presence of Styles or StyleSheets to be a viable means of hiding the text either. Also in the `dlgConnection` class: * refactor out some highly repetitive code that sets up the predefined Mud icons. * remove warnings and adopts a change introduced into Qt 5.15 for the QLabel::pixmap() which now requires a Qt::ReturnByValue argument to prevent an obsolete code warning and to force a QPixmap to be returned by value and not reference... * remove a warning about: QListWidget::setItemSelected(QListWidgetItem*,bool) being deprecated and switches to the recommended: QListWidgetItem::setSelected(bool) Edit: the last item above may have become redundant as further commits amalgamated into this PR may have taken out these lines anyhow! Change the custom icon generator so that there is NOT a dependency on the position in the list for the colours used. This means that the same name will always have the same colours - which will help the user to pick out a particular profile as it will always look the same no matter if other profiles are added or removed. Remove the colour gradient in the custom icon as it is not thought to be useful nowadays. Refactor out the colour icon generator to a single method that is called from more than one place. BugFix: restore odd original way of accessing some QPixmaps This was causing failures in some CI platforms using Qt 5.14. or older. Revise: really frob the custom icon generator This is to try and produce as varied but still readable icons when they are generated entirely on the basis of the MUD name - in a reproducible (though Qt do NOT guarantee repeatable qHash() functionality across Qt versions - so they may not stay *that* constant) manner. Refrob: use a table of fixed colours Using only 15 hues but with 3 different saturation level plus 5 grey-scale one should give enough different icons but not generate them too close to each other for those with normal colour vision... Signed-off-by: Stephen Lyons <slysven@virginmedia.com> Co-authored-by: Kebap <kebap_spam@gmx.net>
2020-12-11 17:53:28 +00:00
notificationAreaIconLabelInformation->setPixmap(holdPixmap);
2010-03-15 09:37:16 +01:00
// selection mode is important. if this is not set the selection behaviour is
// undefined. this is an undocumented qt bug, as it only shows on certain OS
// and certain architectures.
2010-03-15 09:37:16 +01:00
listWidget_profiles->setSelectionMode(QAbstractItemView::SingleSelection);
listWidget_profiles->setContextMenuPolicy(Qt::CustomContextMenu);
connect(listWidget_profiles, &QWidget::customContextMenuRequested, this, &dlgConnectionProfiles::slot_profileContextMenu);
2010-03-15 09:37:16 +01:00
mpTabBar = new QTabBar(this);
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
// QTabWidget gives this dialog a second QTabBar, so this one needs a name
mpTabBar->setObjectName(qsl("gamesTabBar"));
//: Tab showing only the games the user already has profiles for
mpTabBar->insertTab(scmMyGamesTab, tr("My games"));
//: Tab showing every game Mudlet has a built-in profile for
mpTabBar->insertTab(scmAllGamesTab, tr("All games"));
mpTabBar->setExpanding(false);
mpTabBar->setAccessibleName(tr("games shown"));
mpTabBar->setAccessibleDescription(tr("Switch between showing only your own games and all of the games Mudlet knows about."));
verticalLayout_gamesList->insertWidget(0, mpTabBar);
setTabOrder(mpTabBar, listWidget_profiles);
if (!mudlet::self()->mOnlyShownPredefinedProfiles.isEmpty()) {
// dedicated single-game builds only ever show their own game(s), so
// there is nothing to switch between
mpTabBar->hide();
} else {
auto& settings = *mudlet::self()->mpSettings;
int initialTab = scmMyGamesTab;
if (settings.contains(qsl("connectionDialogActiveTab"))) {
initialTab = settings.value(qsl("connectionDialogActiveTab")).toInt() == scmAllGamesTab ? scmAllGamesTab : scmMyGamesTab;
} else if (settings.value(qsl("showOnlyMyProfiles"), false).toBool()) {
// migrate the retired "Show my profiles only" context menu filter,
// which the "My games" tab replaces
initialTab = scmMyGamesTab;
settings.setValue(qsl("connectionDialogActiveTab"), initialTab);
} else if (QDir(mudlet::getMudletPath(enums::profilesPath)).entryList(QDir::Dirs | QDir::NoDotAndDotDot).isEmpty()) {
// a newcomer has no profiles yet, so show them the catalog
initialTab = scmAllGamesTab;
}
// the retired filter's setting is dropped even when it was false, so it
// cannot resurface should connectionDialogActiveTab ever go missing
settings.remove(qsl("showOnlyMyProfiles"));
mpTabBar->setCurrentIndex(initialTab);
}
// connected only after the initial tab is set, so that setting it is not
// mistaken for the user switching tabs
connect(mpTabBar, &QTabBar::currentChanged, this, &dlgConnectionProfiles::slot_activeTabChanged);
QAbstractButton* abort = dialog_buttonbox->button(QDialogButtonBox::Cancel);
connect_button = dialog_buttonbox->addButton(tr("Connect"), QDialogButtonBox::AcceptRole);
connect_button->setAccessibleDescription(btn_connOrLoad_disabled_accessDesc);
offline_button = dialog_buttonbox->addButton(tr("Offline"), QDialogButtonBox::AcceptRole);
offline_button->setAccessibleDescription(btn_connOrLoad_disabled_accessDesc);
Enhance: make Mudlet respect icons on dialog buttons system setting (#1757) At least on X11 based OSes it is possible for the Window Manager to change the styling/images used for icons on native dialog boxes - or even hide them. This hides/shows the icons on dialogues (currently only the "connection preferences") so that they match the system settings that the Qt generated dialogues already respect. There is not a QApplication::getSetting(option) means to do this - instead we have to see whether the buttons on a Qt generated dialogue using a predefined button role has an icon and use it's presence as an indication. This commit also overcomes the problem that the icons would previously have shown as the "baked-in" icons in the "Welcome message" even if they were not present on the buttons themselves. In the future it will be desirable to also use QIcon::fromTheme(...) to furnish the icons for other dialogues and also respect user/system settings for showing/hiding icons on dialogue buttons where we add buttons ourselves to such QDialogButtonBox - from past investigations this is particularly going to apply to the Pack Manager; Module Manager and Package Exporter dialogue as they current have an irregular set of buttons that I plan to "tidy-up" by moving more of them into the button-box on their dialogues. Also: * delete now unused icon: * /src/icons/list-add_small.png * replace with a "better" icon on the Connect Profile dialog (but used elsewhere also and left unchanged there): * /src/icons/edit-delete-shred.png ==> /src/icons/edit-delete.png copy profile button: * /src/icons/list-add_small.png ==> /src/icons/edit-copy.png new profile button: * /src/icons/list-add_small.png ==> /src/icons/document-new.png Removed: * empty #if defined(INCLUDE_UPDATER) code block from mudlet.cpp Signed-off-by: Stephen Lyons <slysven@virginmedia.com>
2018-06-11 15:08:35 +00:00
//: Button shown on first launch to skip the tutorial and show the full games list
mpSkipToGamesButton = new QPushButton(tr("Skip - show me the games list"), this);
mpSkipToGamesButton->setObjectName(qsl("skipToGamesButton"));
mpSkipToGamesButton->hide();
horizontalLayout_3->insertWidget(0, mpSkipToGamesButton);
connect(mpSkipToGamesButton, &QPushButton::clicked, this, &dlgConnectionProfiles::slot_skipToGamesList);
// Pressing Enter in the connection form must always mean "Connect". The
// tutorial invitation hides the Connect button on first show, which stops
// Qt's automatic default-button tracking from ever settling on it - Enter
// would then activate the first autoDefault button in the dialog, which
// happens to be Remove, silently deleting the profile being created:
remove_profile_button->setAutoDefault(false);
new_profile_button->setAutoDefault(false);
mpSkipToGamesButton->setAutoDefault(false);
connect_button->setDefault(true);
Enhance: make Mudlet respect icons on dialog buttons system setting (#1757) At least on X11 based OSes it is possible for the Window Manager to change the styling/images used for icons on native dialog boxes - or even hide them. This hides/shows the icons on dialogues (currently only the "connection preferences") so that they match the system settings that the Qt generated dialogues already respect. There is not a QApplication::getSetting(option) means to do this - instead we have to see whether the buttons on a Qt generated dialogue using a predefined button role has an icon and use it's presence as an indication. This commit also overcomes the problem that the icons would previously have shown as the "baked-in" icons in the "Welcome message" even if they were not present on the buttons themselves. In the future it will be desirable to also use QIcon::fromTheme(...) to furnish the icons for other dialogues and also respect user/system settings for showing/hiding icons on dialogue buttons where we add buttons ourselves to such QDialogButtonBox - from past investigations this is particularly going to apply to the Pack Manager; Module Manager and Package Exporter dialogue as they current have an irregular set of buttons that I plan to "tidy-up" by moving more of them into the button-box on their dialogues. Also: * delete now unused icon: * /src/icons/list-add_small.png * replace with a "better" icon on the Connect Profile dialog (but used elsewhere also and left unchanged there): * /src/icons/edit-delete-shred.png ==> /src/icons/edit-delete.png copy profile button: * /src/icons/list-add_small.png ==> /src/icons/edit-copy.png new profile button: * /src/icons/list-add_small.png ==> /src/icons/document-new.png Removed: * empty #if defined(INCLUDE_UPDATER) code block from mudlet.cpp Signed-off-by: Stephen Lyons <slysven@virginmedia.com>
2018-06-11 15:08:35 +00:00
// Test and set if needed mudlet::mIsIconShownOnDialogButtonBoxes - if there
// is already a Qt provided icon on a predefined button, this is probably
// the first and best place to test this as the "Cancel" button is a built-
// in dialog button which will have an icon if the current system style
// settings suggest it:
mudlet::self()->mShowIconsOnDialogs = !abort->icon().isNull();
//: Welcome message shown on first launch, focused on starting the tutorial.
auto Welcome_text_template = tr("<p><center><img src=\"tutorialIcon\"/></center></p>"
"<p><center><big><b>Welcome to Mudlet!</b></big></center></p>"
"<p><center>Play a short guided adventure to learn<br>"
"how to navigate in games, use triggers, aliases, and scripting.</center></p>"
"<p><center><a href=\"mudlet-tutorial\">Start Tutorial</a></center></p>"
"<p align=\"right\"><span style=\" font-family:'Sans';\">The Mudlet Team </span>"
"<img src=\":/icons/mudlet_main_16px.png\"/></p>");
Enhance: make Mudlet respect icons on dialog buttons system setting (#1757) At least on X11 based OSes it is possible for the Window Manager to change the styling/images used for icons on native dialog boxes - or even hide them. This hides/shows the icons on dialogues (currently only the "connection preferences") so that they match the system settings that the Qt generated dialogues already respect. There is not a QApplication::getSetting(option) means to do this - instead we have to see whether the buttons on a Qt generated dialogue using a predefined button role has an icon and use it's presence as an indication. This commit also overcomes the problem that the icons would previously have shown as the "baked-in" icons in the "Welcome message" even if they were not present on the buttons themselves. In the future it will be desirable to also use QIcon::fromTheme(...) to furnish the icons for other dialogues and also respect user/system settings for showing/hiding icons on dialogue buttons where we add buttons ourselves to such QDialogButtonBox - from past investigations this is particularly going to apply to the Pack Manager; Module Manager and Package Exporter dialogue as they current have an irregular set of buttons that I plan to "tidy-up" by moving more of them into the button-box on their dialogues. Also: * delete now unused icon: * /src/icons/list-add_small.png * replace with a "better" icon on the Connect Profile dialog (but used elsewhere also and left unchanged there): * /src/icons/edit-delete-shred.png ==> /src/icons/edit-delete.png copy profile button: * /src/icons/list-add_small.png ==> /src/icons/edit-copy.png new profile button: * /src/icons/list-add_small.png ==> /src/icons/document-new.png Removed: * empty #if defined(INCLUDE_UPDATER) code block from mudlet.cpp Signed-off-by: Stephen Lyons <slysven@virginmedia.com>
2018-06-11 15:08:35 +00:00
auto pWelcome_document = new QTextDocument(this);
QPixmap tutorialIcon(qsl(":/icons/mudlet-tutorial.png"));
tutorialIcon = tutorialIcon.scaled(160, 40, Qt::KeepAspectRatio, Qt::SmoothTransformation);
pWelcome_document->addResource(QTextDocument::ImageResource, QUrl(qsl("tutorialIcon")), tutorialIcon);
mpCopyProfile = new QAction(tr("Copy"), this);
mpCopyProfile->setObjectName(qsl("copyProfile"));
auto copyProfileSettings = new QAction(tr("Copy settings only"), this);
copyProfileSettings->setObjectName(qsl("copyProfileSettingsOnly"));
copy_profile_toolbutton->addAction(mpCopyProfile);
copy_profile_toolbutton->addAction(copyProfileSettings);
copy_profile_toolbutton->setDefaultAction(mpCopyProfile);
auto objectList = mpCopyProfile->associatedObjects();
QList<QWidget*> widgetList;
for (const auto pObjectItem : std::as_const(objectList)) {
auto pWidgetItem = qobject_cast<QWidget*>(pObjectItem);
if (pWidgetItem) {
widgetList << pWidgetItem;
}
}
Q_ASSERT_X(!widgetList.isEmpty(), "dlgConnectionProfiles::dlgConnectionProfiles(...)", "A QWidget for mpCopyProfile QAction not found.");
widgetList.first()->setAccessibleName(tr("copy profile"));
widgetList.first()->setAccessibleDescription(tr("copy the entire profile to new one that will require a different new name."));
objectList = copyProfileSettings->associatedObjects();
widgetList.clear();
for (const auto pObjectItem : std::as_const(objectList)) {
auto pWidgetItem = qobject_cast<QWidget*>(pObjectItem);
if (pWidgetItem) {
widgetList << pWidgetItem;
}
}
Q_ASSERT_X(!widgetList.isEmpty(), "dlgConnectionProfiles::dlgConnectionProfiles(...)", "A QWidget for copyProfileSettings QAction not found.");
widgetList.first()->setAccessibleName(tr("copy profile settings"));
widgetList.first()->setAccessibleDescription(tr("copy the settings and some other parts of the profile to a new one that will require a different new name."));
Enhance: make Mudlet respect icons on dialog buttons system setting (#1757) At least on X11 based OSes it is possible for the Window Manager to change the styling/images used for icons on native dialog boxes - or even hide them. This hides/shows the icons on dialogues (currently only the "connection preferences") so that they match the system settings that the Qt generated dialogues already respect. There is not a QApplication::getSetting(option) means to do this - instead we have to see whether the buttons on a Qt generated dialogue using a predefined button role has an icon and use it's presence as an indication. This commit also overcomes the problem that the icons would previously have shown as the "baked-in" icons in the "Welcome message" even if they were not present on the buttons themselves. In the future it will be desirable to also use QIcon::fromTheme(...) to furnish the icons for other dialogues and also respect user/system settings for showing/hiding icons on dialogue buttons where we add buttons ourselves to such QDialogButtonBox - from past investigations this is particularly going to apply to the Pack Manager; Module Manager and Package Exporter dialogue as they current have an irregular set of buttons that I plan to "tidy-up" by moving more of them into the button-box on their dialogues. Also: * delete now unused icon: * /src/icons/list-add_small.png * replace with a "better" icon on the Connect Profile dialog (but used elsewhere also and left unchanged there): * /src/icons/edit-delete-shred.png ==> /src/icons/edit-delete.png copy profile button: * /src/icons/list-add_small.png ==> /src/icons/edit-copy.png new profile button: * /src/icons/list-add_small.png ==> /src/icons/document-new.png Removed: * empty #if defined(INCLUDE_UPDATER) code block from mudlet.cpp Signed-off-by: Stephen Lyons <slysven@virginmedia.com>
2018-06-11 15:08:35 +00:00
if (mudlet::self()->mShowIconsOnDialogs) {
// As we are repurposing the cancel to be a close button we do want to
// change it anyhow:
abort->setIcon(QIcon::fromTheme(qsl("dialog-close"), QIcon(qsl(":/icons/dialog-close.png"))));
Enhance: make Mudlet respect icons on dialog buttons system setting (#1757) At least on X11 based OSes it is possible for the Window Manager to change the styling/images used for icons on native dialog boxes - or even hide them. This hides/shows the icons on dialogues (currently only the "connection preferences") so that they match the system settings that the Qt generated dialogues already respect. There is not a QApplication::getSetting(option) means to do this - instead we have to see whether the buttons on a Qt generated dialogue using a predefined button role has an icon and use it's presence as an indication. This commit also overcomes the problem that the icons would previously have shown as the "baked-in" icons in the "Welcome message" even if they were not present on the buttons themselves. In the future it will be desirable to also use QIcon::fromTheme(...) to furnish the icons for other dialogues and also respect user/system settings for showing/hiding icons on dialogue buttons where we add buttons ourselves to such QDialogButtonBox - from past investigations this is particularly going to apply to the Pack Manager; Module Manager and Package Exporter dialogue as they current have an irregular set of buttons that I plan to "tidy-up" by moving more of them into the button-box on their dialogues. Also: * delete now unused icon: * /src/icons/list-add_small.png * replace with a "better" icon on the Connect Profile dialog (but used elsewhere also and left unchanged there): * /src/icons/edit-delete-shred.png ==> /src/icons/edit-delete.png copy profile button: * /src/icons/list-add_small.png ==> /src/icons/edit-copy.png new profile button: * /src/icons/list-add_small.png ==> /src/icons/document-new.png Removed: * empty #if defined(INCLUDE_UPDATER) code block from mudlet.cpp Signed-off-by: Stephen Lyons <slysven@virginmedia.com>
2018-06-11 15:08:35 +00:00
const QIcon icon_new(QIcon::fromTheme(qsl("document-new"), QIcon(qsl(":/icons/document-new.png"))));
const QIcon icon_connect(QIcon::fromTheme(qsl("dialog-ok-apply"), QIcon(qsl(":/icons/preferences-web-browser-cache.png"))));
Enhance: make Mudlet respect icons on dialog buttons system setting (#1757) At least on X11 based OSes it is possible for the Window Manager to change the styling/images used for icons on native dialog boxes - or even hide them. This hides/shows the icons on dialogues (currently only the "connection preferences") so that they match the system settings that the Qt generated dialogues already respect. There is not a QApplication::getSetting(option) means to do this - instead we have to see whether the buttons on a Qt generated dialogue using a predefined button role has an icon and use it's presence as an indication. This commit also overcomes the problem that the icons would previously have shown as the "baked-in" icons in the "Welcome message" even if they were not present on the buttons themselves. In the future it will be desirable to also use QIcon::fromTheme(...) to furnish the icons for other dialogues and also respect user/system settings for showing/hiding icons on dialogue buttons where we add buttons ourselves to such QDialogButtonBox - from past investigations this is particularly going to apply to the Pack Manager; Module Manager and Package Exporter dialogue as they current have an irregular set of buttons that I plan to "tidy-up" by moving more of them into the button-box on their dialogues. Also: * delete now unused icon: * /src/icons/list-add_small.png * replace with a "better" icon on the Connect Profile dialog (but used elsewhere also and left unchanged there): * /src/icons/edit-delete-shred.png ==> /src/icons/edit-delete.png copy profile button: * /src/icons/list-add_small.png ==> /src/icons/edit-copy.png new profile button: * /src/icons/list-add_small.png ==> /src/icons/document-new.png Removed: * empty #if defined(INCLUDE_UPDATER) code block from mudlet.cpp Signed-off-by: Stephen Lyons <slysven@virginmedia.com>
2018-06-11 15:08:35 +00:00
offline_button->setIcon(QIcon(qsl(":/icons/mudlet_editor.png")));
connect_button->setIcon(icon_connect);
Enhance: make Mudlet respect icons on dialog buttons system setting (#1757) At least on X11 based OSes it is possible for the Window Manager to change the styling/images used for icons on native dialog boxes - or even hide them. This hides/shows the icons on dialogues (currently only the "connection preferences") so that they match the system settings that the Qt generated dialogues already respect. There is not a QApplication::getSetting(option) means to do this - instead we have to see whether the buttons on a Qt generated dialogue using a predefined button role has an icon and use it's presence as an indication. This commit also overcomes the problem that the icons would previously have shown as the "baked-in" icons in the "Welcome message" even if they were not present on the buttons themselves. In the future it will be desirable to also use QIcon::fromTheme(...) to furnish the icons for other dialogues and also respect user/system settings for showing/hiding icons on dialogue buttons where we add buttons ourselves to such QDialogButtonBox - from past investigations this is particularly going to apply to the Pack Manager; Module Manager and Package Exporter dialogue as they current have an irregular set of buttons that I plan to "tidy-up" by moving more of them into the button-box on their dialogues. Also: * delete now unused icon: * /src/icons/list-add_small.png * replace with a "better" icon on the Connect Profile dialog (but used elsewhere also and left unchanged there): * /src/icons/edit-delete-shred.png ==> /src/icons/edit-delete.png copy profile button: * /src/icons/list-add_small.png ==> /src/icons/edit-copy.png new profile button: * /src/icons/list-add_small.png ==> /src/icons/document-new.png Removed: * empty #if defined(INCLUDE_UPDATER) code block from mudlet.cpp Signed-off-by: Stephen Lyons <slysven@virginmedia.com>
2018-06-11 15:08:35 +00:00
new_profile_button->setIcon(icon_new);
remove_profile_button->setIcon(QIcon::fromTheme(qsl("edit-delete"), QIcon(qsl(":/icons/edit-delete.png"))));
Enhance: make Mudlet respect icons on dialog buttons system setting (#1757) At least on X11 based OSes it is possible for the Window Manager to change the styling/images used for icons on native dialog boxes - or even hide them. This hides/shows the icons on dialogues (currently only the "connection preferences") so that they match the system settings that the Qt generated dialogues already respect. There is not a QApplication::getSetting(option) means to do this - instead we have to see whether the buttons on a Qt generated dialogue using a predefined button role has an icon and use it's presence as an indication. This commit also overcomes the problem that the icons would previously have shown as the "baked-in" icons in the "Welcome message" even if they were not present on the buttons themselves. In the future it will be desirable to also use QIcon::fromTheme(...) to furnish the icons for other dialogues and also respect user/system settings for showing/hiding icons on dialogue buttons where we add buttons ourselves to such QDialogButtonBox - from past investigations this is particularly going to apply to the Pack Manager; Module Manager and Package Exporter dialogue as they current have an irregular set of buttons that I plan to "tidy-up" by moving more of them into the button-box on their dialogues. Also: * delete now unused icon: * /src/icons/list-add_small.png * replace with a "better" icon on the Connect Profile dialog (but used elsewhere also and left unchanged there): * /src/icons/edit-delete-shred.png ==> /src/icons/edit-delete.png copy profile button: * /src/icons/list-add_small.png ==> /src/icons/edit-copy.png new profile button: * /src/icons/list-add_small.png ==> /src/icons/document-new.png Removed: * empty #if defined(INCLUDE_UPDATER) code block from mudlet.cpp Signed-off-by: Stephen Lyons <slysven@virginmedia.com>
2018-06-11 15:08:35 +00:00
copy_profile_toolbutton->setIcon(QIcon::fromTheme(qsl("edit-copy"), QIcon(qsl(":/icons/edit-copy.png"))));
copy_profile_toolbutton->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
mpCopyProfile->setIcon(QIcon::fromTheme(qsl("edit-copy"), QIcon(qsl(":/icons/edit-copy.png"))));
Enhance: make Mudlet respect icons on dialog buttons system setting (#1757) At least on X11 based OSes it is possible for the Window Manager to change the styling/images used for icons on native dialog boxes - or even hide them. This hides/shows the icons on dialogues (currently only the "connection preferences") so that they match the system settings that the Qt generated dialogues already respect. There is not a QApplication::getSetting(option) means to do this - instead we have to see whether the buttons on a Qt generated dialogue using a predefined button role has an icon and use it's presence as an indication. This commit also overcomes the problem that the icons would previously have shown as the "baked-in" icons in the "Welcome message" even if they were not present on the buttons themselves. In the future it will be desirable to also use QIcon::fromTheme(...) to furnish the icons for other dialogues and also respect user/system settings for showing/hiding icons on dialogue buttons where we add buttons ourselves to such QDialogButtonBox - from past investigations this is particularly going to apply to the Pack Manager; Module Manager and Package Exporter dialogue as they current have an irregular set of buttons that I plan to "tidy-up" by moving more of them into the button-box on their dialogues. Also: * delete now unused icon: * /src/icons/list-add_small.png * replace with a "better" icon on the Connect Profile dialog (but used elsewhere also and left unchanged there): * /src/icons/edit-delete-shred.png ==> /src/icons/edit-delete.png copy profile button: * /src/icons/list-add_small.png ==> /src/icons/edit-copy.png new profile button: * /src/icons/list-add_small.png ==> /src/icons/document-new.png Removed: * empty #if defined(INCLUDE_UPDATER) code block from mudlet.cpp Signed-off-by: Stephen Lyons <slysven@virginmedia.com>
2018-06-11 15:08:35 +00:00
}
pWelcome_document->setHtml(qsl("<html><head/><body>%1</body></html>").arg(Welcome_text_template));
Enhance: make Mudlet respect icons on dialog buttons system setting (#1757) At least on X11 based OSes it is possible for the Window Manager to change the styling/images used for icons on native dialog boxes - or even hide them. This hides/shows the icons on dialogues (currently only the "connection preferences") so that they match the system settings that the Qt generated dialogues already respect. There is not a QApplication::getSetting(option) means to do this - instead we have to see whether the buttons on a Qt generated dialogue using a predefined button role has an icon and use it's presence as an indication. This commit also overcomes the problem that the icons would previously have shown as the "baked-in" icons in the "Welcome message" even if they were not present on the buttons themselves. In the future it will be desirable to also use QIcon::fromTheme(...) to furnish the icons for other dialogues and also respect user/system settings for showing/hiding icons on dialogue buttons where we add buttons ourselves to such QDialogButtonBox - from past investigations this is particularly going to apply to the Pack Manager; Module Manager and Package Exporter dialogue as they current have an irregular set of buttons that I plan to "tidy-up" by moving more of them into the button-box on their dialogues. Also: * delete now unused icon: * /src/icons/list-add_small.png * replace with a "better" icon on the Connect Profile dialog (but used elsewhere also and left unchanged there): * /src/icons/edit-delete-shred.png ==> /src/icons/edit-delete.png copy profile button: * /src/icons/list-add_small.png ==> /src/icons/edit-copy.png new profile button: * /src/icons/list-add_small.png ==> /src/icons/document-new.png Removed: * empty #if defined(INCLUDE_UPDATER) code block from mudlet.cpp Signed-off-by: Stephen Lyons <slysven@virginmedia.com>
2018-06-11 15:08:35 +00:00
welcome_message->setDocument(pWelcome_document);
welcome_message->setOpenLinks(false);
welcome_message->setOpenExternalLinks(false);
connect(welcome_message, &QTextBrowser::anchorClicked, this, [this](const QUrl& link) {
if (link.toString() == qsl("mudlet-tutorial")) {
mTutorialDismissed = true;
profile_name_entry->setText(qsl("Mudlet Tutorial"));
host_name_entry->setText(qsl("localhost"));
port_entry->setText(qsl("0"));
validName = true;
validUrl = true;
validPort = true;
loadProfile(true);
}
});
mpAction_revealPassword = new QAction(this);
mpAction_revealPassword->setCheckable(true);
mpAction_revealPassword->setObjectName(qsl("mpAction_revealPassword"));
slot_togglePasswordVisibility(false);
character_password_entry->addAction(mpAction_revealPassword, QLineEdit::TrailingPosition);
if (mudlet::self()->storingPasswordsSecurely()) {
character_password_entry->setToolTip(utils::richText(tr("Characters password, stored securely in the computer's credential manager")));
} else {
character_password_entry->setToolTip(utils::richText(tr("Characters password. Note that the password is not encrypted in storage")));
}
connect(mpAction_revealPassword, &QAction::triggered, this, &dlgConnectionProfiles::slot_togglePasswordVisibility);
connect(offline_button, &QAbstractButton::clicked, this, &dlgConnectionProfiles::slot_load);
connect(connect_button, &QAbstractButton::clicked, this, &dlgConnectionProfiles::accept);
connect(abort, &QAbstractButton::clicked, this, &dlgConnectionProfiles::slot_cancel);
connect(new_profile_button, &QAbstractButton::clicked, this, &dlgConnectionProfiles::slot_addProfile);
Infrastructure: tidy up naming of SLOT methods and their usage - Part 5 (last) (#6266) When Qt's slot/signal system is used to invoke a method there is some overhead - so it makes sense to ensure developers can spot all such methods (functions). We have tried to do this with a `slot_` prefix to the methods we create but it has not been applied uniformly. This PR is intended to conclude helping with this by more rigorously doing so - the names changed herein should all follow a `slot_`*camelCaseMethodName* style. In addition some methods were detected that are not currently used or which are not actually used via the signal/slot system, these have been commented out or have had the prefix removed and the declaration in the relevant header file moved as appropriate. There were two similar sets of (not prefixed with `slot_`) slots in the Profile preferences class that adjusted the colour settings separately for the main console and the mapper the latter had the same names but with a `2` suffix. To made it more clear I have changed them to include `Map` in their names instead. Also, the NON-slot method: `(void) dlgProfilePreferences::setColor(QPushButton*, QColor&, bool)` has been renamed to: `(void) dlgProfilePreferences::setButtonAndProfileColor(QPushButton*, QColor&, bool)` so that it is clearly distinguishable from built in Qt methods that are also called `setColor` - though which do have different signatures! For reference the changes made are: * `TConsole::slot_stop_all_triggers(...)` ==> `TConsole::slot_stopAllItems(...)` * `dlgConnectionProfiles::slot_copy_profile()` ==> `dlgConnectionProfiles::slot_copyProfile()` * `dlgConnectionProfiles::slot_copy_profilesettings_only()` ==> `dlgConnectionProfiles::slot_copyOnlySettingsOfProfile()` * `dlgConnectionProfiles::slot_deleteprofile_check(...)` ==> `dlgConnectionProfiles::slot_deleteProfileCheck(...)` * `dlgConnectionProfiles::slot_password_deleted(...)` ==> `dlgConnectionProfiles::slot_passwordDeleted(...)` * `dlgConnectionProfiles::slot_password_saved(...) ==> `dlgConnectionProfiles::slot_passwordSaved(...)` * `dlgConnectionProfiles::slot_profile_menu(...)` ==> `dlgConnectionProfiles::slot_profileContextMenu(...)` * `dlgConnectionProfiles::slot_reset_custom_icon()` ==> `dlgConnectionProfiles::slot_resetCustomIcon()` * `dlgConnectionProfiles::slot_set_custom_icon()` ==> `dlgConnectionProfiles::slot_setCustomIcon()` * `dlgConnectionProfiles::slot_set_custom_color()` ==> `dlgConnectionProfiles::slot_setCustomColor()` * `dlgConnectionProfiles::slot_update_autologin(...)` ==> `dlgConnectionProfiles::slot_updateAutoConnect(...)` * `dlgConnectionProfiles::slot_update_autoreconnect(...)` ==> `dlgConnectionProfiles::slot_updateAutoReconnect(...)` * `dlgConnectionProfiles::slot_update_description()` ==> `dlgConnectionProfiles::slot_updateDescription()` * `dlgConnectionProfiles::slot_update_discord_optin(...)` ==> `dlgConnectionProfiles::slot_updateDiscordOptIn(...)` * `dlgProfilePreferences::copyMap()` ==> `dlgProfilePreferences::slot_copyMap()` * `dlgProfilePreferences::downloadMap()` ==> `dlgProfilePreferences::slot_downloadMap()` * `dlgProfilePreferences::hideActionLabel()` ==> `dlgProfilePreferences::slot_hideActionLabel()` * `dlgProfilePreferences::loadMap()` ==> `dlgProfilePreferences::slot_loadMap()` * `dlgProfilePreferences::resetColors()` ==> `dlgProfilePreferences::slot_resetColors()` * `dlgProfilePreferences::resetColors2()` ==> `dlgProfilePreferences::slot_resetMapColors()` * `dlgProfilePreferences::saveMap()` ==> `dlgProfilePreferences::slot_saveMap()` * `dlgProfilePreferences::setBgColor()` ==> `dlgProfilePreferences::slot_setBgColor()` * `dlgProfilePreferences::setBgColor2()` ==> `dlgProfilePreferences::slot_setMapBgColor()` * `dlgProfilePreferences::setColorBlack()` ==> `dlgProfilePreferences::slot_setColorBlack()` * `dlgProfilePreferences::setColorBlack2()` ==> `dlgProfilePreferences::slot_setMapColorBlack()` * `dlgProfilePreferences::setColorBlue()` ==> `dlgProfilePreferences::slot_setColorBlue()` * `dlgProfilePreferences::setColorCyan()` ==> `dlgProfilePreferences::slot_setColorCyan()` * `dlgProfilePreferences::setColorBlue2()` ==> `dlgProfilePreferences::slot_setMapColorBlue()` * `dlgProfilePreferences::setColorCyan2()` ==> `dlgProfilePreferences::slot_setMapColorCyan()` * `dlgProfilePreferences::setColorGreen()` ==> `dlgProfilePreferences::slot_setColorGreen()` * `dlgProfilePreferences::setColorGreen2()` ==> `dlgProfilePreferences::slot_setMapColorGreen()` * `dlgProfilePreferences::setColorLightBlack()` ==> `dlgProfilePreferences::slot_setColorLightBlack()` * `dlgProfilePreferences::setColorLightBlack2()` ==> `dlgProfilePreferences::slot_setMapColorLightBlack()` * `dlgProfilePreferences::setColorLightBlue()` ==> `dlgProfilePreferences::slot_setColorLightBlue()` * `dlgProfilePreferences::setColorLightBlue2()` ==> `dlgProfilePreferences::slot_setMapColorLightBlue()` * `dlgProfilePreferences::setColorLightCyan()` ==> `dlgProfilePreferences::slot_setColorLightCyan()` * `dlgProfilePreferences::setColorLightCyan2()` ==> `dlgProfilePreferences::slot_setMapColorLightCyan()` * `dlgProfilePreferences::setColorLightGreen()` ==> `dlgProfilePreferences::slot_setColorLightGreen()` * `dlgProfilePreferences::setColorLightGreen2()` ==> `dlgProfilePreferences::slot_setMapColorLightGreen()` * `dlgProfilePreferences::setColorLightMagenta()` ==> `dlgProfilePreferences::slot_setColorLightMagenta()` * `dlgProfilePreferences::setColorLightMagenta2()` ==> `dlgProfilePreferences::slot_setMapColorLightMagenta()` * `dlgProfilePreferences::setColorLightRed()` ==> `dlgProfilePreferences::slot_setColorLightRed()` * `dlgProfilePreferences::setColorLightRed2()` ==> `dlgProfilePreferences::slot_setMapColorLightRed()` * `dlgProfilePreferences::setColorLightWhite()` ==> `dlgProfilePreferences::slot_setColorLightWhite()` * `dlgProfilePreferences::setColorLightWhite2()` ==> `dlgProfilePreferences::slot_setMapColorLightWhite()` * `dlgProfilePreferences::setColorLightYellow()` ==> `dlgProfilePreferences::slot_setColorLightYellow()` * `dlgProfilePreferences::setColorLightYellow2()` ==> `dlgProfilePreferences::slot_setMapColorLightYellow()` * `dlgProfilePreferences::setColorMagenta()` ==> `dlgProfilePreferences::slot_setColorMagenta()` * `dlgProfilePreferences::setColorMagenta2()` ==> `dlgProfilePreferences::slot_setMapColorMagenta()` * `dlgProfilePreferences::setColorRed2()` ==> `dlgProfilePreferences::slot_setMapColorRed()` * `dlgProfilePreferences::setColorRed()` ==> `dlgProfilePreferences::slot_setColorRed()` * `dlgProfilePreferences::setColorWhite()` ==> `dlgProfilePreferences::slot_setColorWhite()` * `dlgProfilePreferences::setColorWhite2()` ==> `dlgProfilePreferences::slot_setMapColorWhite()` * `dlgProfilePreferences::setColorYellow()` ==> `dlgProfilePreferences::slot_setColorYellow()` * `dlgProfilePreferences::setColorYellow2()` ==> `dlgProfilePreferences::slot_setMapColorYellow()` * `dlgProfilePreferences::setCommandBgColor()` ==> `dlgProfilePreferences::slot_setCommandBgColor()` * `dlgProfilePreferences::setCommandFgColor()` ==> `dlgProfilePreferences::slot_setCommandFgColor()` * `dlgProfilePreferences::setCommandLineBgColor()` ==> `dlgProfilePreferences::slot_setCommandLineBgColor()` * `dlgProfilePreferences::setCommandLineFgColor()` ==> `dlgProfilePreferences::slot_setCommandLineFgColor()` * `dlgProfilePreferences::setDisplayFont()` ==> `dlgProfilePreferences::slot_setDisplayFont()` * `dlgProfilePreferences::setFgColor()` ==> `dlgProfilePreferences::slot_setFgColor()` * `dlgProfilePreferences::setFgColor2()` ==> `dlgProfilePreferences::slot_setMapExitsColor()` * `dlgProfilePreferences::setFontSize()` ==> `dlgProfilePreferences::slot_setFontSize()` * `dlgProfilePreferences::setMapInfoBackground()` ==> `dlgProfilePreferences::slot_setMapInfoBgColor()` * `dlgProfilePreferences::setRoomBorderColor()` ==> `dlgProfilePreferences::slot_setMapRoomBorderColor()` * `dlgProfilePreferences::slot_script_selected(...)` ==> `dlgProfilePreferences::slot_scriptSelected(...)` * `dlgProfilePreferences::slot_theme_selected(...)` ==> `dlgProfilePreferences::slot_themeSelected(...)` * `dlgRoomSymbol::colorRejected()` ==> `dlgRoomSymbol::slot_colorRejected()` * `dlgRoomSymbol::colorSelected(...)` ==> `dlgRoomSymbol::slot_colorSelected(...)` * `dlgRoomSymbol::currentColorChanged(...)` ==> `dlgRoomSymbol::slot_currentColorChanged(...)` * `dlgRoomSymbol::openColorSelector()` ==> `dlgRoomSymbol::slot_openColorSelector()` * `dlgRoomSymbol::resetColor()` ==> `dlgRoomSymbol::slot_resetColors()` * `dlgRoomSymbol::updatePreview()` ==> `dlgRoomSymbol::slot_updatePreview()` * `dlgTriggerEditor::slot_show_vars()` ==> `dlgTriggerEditor::slot_showVariables()` * `dlgTriggerEditor::slot_var_changed(...)` ==> `dlgTriggerEditor::slot_variableChanged(...)` * `dlgTriggerEditor::slot_var_selected(...)` ==> `dlgTriggerEditor::slot_variableSelected(...)` * `mudlet::slot_check_manual_update()` ==> `mudlet::slot_manualUpdateCheck()` * `mudlet::slot_close_current_profile()` ==> `mudlet::slot_closeCurrentProfile()` * `mudlet::slot_close_profile_requested(...)` ==> `mudlet::slot_closeProfileRequested(...)` * `mudlet::slot_connection_dlg_finished(...)` ==> `mudlet::slot_connectionDialogueFinished(...)` * `mudlet::slot_module_manager()` ==> `mudlet::slot_moduleManager()` * `mudlet::slot_mudlet_discord()` ==> `mudlet::slot_mudletDiscord()` * `mudlet::slot_multi_view(...)` ==> `mudlet::slot_multiView(const bool state)` * `mudlet::slot_package_manager()` ==> `mudlet::slot_packageManager()` * `mudlet::slot_package_exporter()` ==> `mudlet::slot_packageExporter()` * `mudlet::slot_timer_fires()` ==> `mudlet::slot_timerFires()` * `mudlet::slot_toggle_multi_view()` ==> `mudlet::slot_toggleMultiView()` Also the names for these in particular have been changed to make more sense: * `dlgProfilePreferences::slot_chooseProfilesChanged()` ==> `dlgProfilePreferences::slot_chosenProfilesChanged()` * `dlgProfilePreferences::slot_editor_tab_selected(...)` ==> `dlgProfilePreferences::slot_tabChanged(...)` * `dlgProfilePreferences::slot_passwords_location_changed(...)` ==> `dlgProfilePreferences::slot_passwordStorageLocationChanged(...)` * `dlgProfilePreferences::slot_save_and_exit()` ==> `dlgProfilePreferences::slot_saveAndClose()` * `dlgTriggerEditor::slot_toggleHiddenVar(...)` ==> `dlgTriggerEditor::slot_hideVariable(...)` - changed to distinguish it from `slot_toggleHiddenVariables(...)` * `dlgTriggerEditor::slot_item_selected_save(...)` ==> `dlgTriggerEditor::slot_saveSelectedItem(...)` * `mudlet::slot_discord()` ==> `mudlet::slot_profileDiscord()` - changed to distinguish it from `slot_mudletDiscord()` Not currently used and commented out: * `dlgProfilePreferences::setCommandLineFont()` ==> `dlgProfilePreferences::slot_setCommandLineFont()` Signed-off-by: Stephen Lyons <slysven@virginmedia.com>
2022-08-29 14:27:13 +02:00
connect(mpCopyProfile, &QAction::triggered, this, &dlgConnectionProfiles::slot_copyProfile);
connect(copyProfileSettings, &QAction::triggered, this, &dlgConnectionProfiles::slot_copyOnlySettingsOfProfile);
connect(remove_profile_button, &QAbstractButton::clicked, this, &dlgConnectionProfiles::slot_deleteProfile);
fix: Enable connect and offline buttons when selecting currently open and then different profile (#7692) <!-- 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 enables the Connect and Offline buttons when changing selection from a currently loaded profile over to another profile that has same server address and port number. #### Motivation for adding to Mudlet I prioritize my time badly and sometimes get the wrong things off my plate first. Though I'm thinking of this as a mental health break from other tasks. #### Other info (issues closed, discussion etc) The changes in #7673 made it so the connection dialog will disable the Connect and Offline buttons when selecting a profile that is already open. But re-enabling the buttons when selecting another profile only happens when other profile has different server address or port. So if you have two profiles for same game, need to select an intermediate one to enable the buttons currently. Whenever you switch to a different profile that has a different server address, the box gets updated which triggers `slot_updateUrl`, which in turn calls `validateProfile()`. Similar deal with `slot_updatePort`. But `slot_updateName` is not similarly called when the name box changes, only when you are typing in the name box, so it only runs `validateProfile()` when typing. I found that the difference between it and the others is `::textChanged` vs `::textEdited`.
2025-02-05 03:02:49 -05:00
connect(profile_name_entry, &QLineEdit::textChanged, this, &dlgConnectionProfiles::slot_updateName);
Infrastructure: tidy up naming of SLOT methods and their usage - Part 2 (#6255) When Qt's slot/signal system is used to invoke a method there is some overhead - so it makes sense to ensure developers can spot all such methods (functions). We have tried to do this with a `slot_` prefix to the methods we create but it has not been applied uniformly. This PR (and one or more to follow) is intended to help with this by more rigorously doing so - the names changed herein should all follow a `slot_`*camelCaseMethodName* style. In addition some methods were detected that are not currently used or which are not actually used via the signal/slot system, these have been commented out or have had the prefix removed and the declaration in the relevant header file moved as appropriate. For reference the changes made are: * `TLuaInterpreter::slotDeleteSender(...)` ==> `TLuaInterpreter::slot_deleteSender(...)` * `TLuaInterpreter::slotPurge()` ==> `TLuaInterpreter::slot_purge()` * `cTelnet::handle_socket_signal_connected()` ==> `cTelnet::slot_socketConnected()` * `cTelnet::handle_socket_signal_disconnected()` ==> `cTelnet::slot_socketDisconnected()` * `cTelnet::handle_socket_signal_hostFound(...)` ==> `cTelnet::slot_socketHostFound(...)` * `cTelnet::handle_socket_signal_readyRead()` ==> `cTelnet::slot_socketReadyToBeRead()` * `cTelnet::handle_socket_signal_sslError(...)` ==> `cTelnet::slot_socketSslError(...)` * `cTelnet::setDownloadProgress(...)` ==> `cTelnet::slot_setDownloadProgress(...)` * `dlgComposer::cancel()` ==> `dlgComposer::slot_cancel()` * `dlgComposer::save()` ==> `dlgComposer::slot_save()` * `dlgConnectionProfiles::slot_item_clicked(...)` ==> `dlgConnectionProfiles::slot_itemClicked(...)` * `dlgConnectionProfiles::slot_save_name()` ==> `dlgConnectionProfiles::slot_saveName()` * `dlgConnectionProfiles::slot_update_login(...)` ==> `dlgConnectionProfiles::slot_updateLogin(...)` * `dlgConnectionProfiles::slot_update_name(...)` ==> `dlgConnectionProfiles::slot_updateName(...)` * `dlgConnectionProfiles::slot_update_pass(...)` ==> `dlgConnectionProfiles::slot_updatePassword(...)` * `dlgConnectionProfiles::slot_update_port(...)` ==> `dlgConnectionProfiles::slot_updatePort(...)` * `dlgConnectionProfiles::slot_update_SSL_TSL_port(...)` ==> `dlgConnectionProfiles::slot_updateSslTslPort(...)` * `dlgConnectionProfiles::slot_update_url(...)` ==> `dlgConnectionProfiles::slot_updateUrl(...)` * `dlgMapLabel::pickBgColor()` ==> `dlgMapLabel::slot_pickBgColor()` * `dlgMapLabel::pickFgColor()` ==> `dlgMapLabel::slot_pickFgColor()` * `dlgMapLabel::pickFile()` ==> `dlgMapLabel::slot_pickFile()` * `dlgMapLabel::pickFont()` ==> `dlgMapLabel::slot_pickFont()` * `dlgMapLabel::save()` ==> `dlgMapLabel::slot_save()` * `dlgMapLabel::updateControls()` ==> `dlgMapLabel::slot_updateControls()` * `dlgMapLabel::updateControlsVisibility()` ==> `dlgMapLabel::slot_updateControlsVisibility()` Commented out as not being used: * `cTelnet::handle_socket_signal_error()` ==> `cTelnet::slot_socketError()` * `dlgConnectionProfiles::slot_update_website(...) ==> `dlgConnectionProfiles::slot_updateWebsite(const QString& url)` I have my doubts about whether the `dlgMapLabel::save()`/`dlgMapLabel::slot_save()` method is needed at all, as it might be that the `QDialog::accept()` slot could have been inserted into the `QObject::connect(...)` call directly instead...? Signed-off-by: Stephen Lyons <slysven@virginmedia.com>
2022-08-27 10:47:01 +02:00
connect(profile_name_entry, &QLineEdit::editingFinished, this, &dlgConnectionProfiles::slot_saveName);
connect(host_name_entry, &QLineEdit::textChanged, this, &dlgConnectionProfiles::slot_updateUrl);
connect(port_entry, &QLineEdit::textChanged, this, &dlgConnectionProfiles::slot_updatePort);
Fix reduce c++20warnings (#7638) #### Brief overview of PR changes/additions 1. Adds explicit ‘this’ or ‘*this’ capture to lambdas where required (not in static ones). 2. Rephrases some combinations of Qt `enum`s that whilst acceptable for C++17 upset the later one. For the non-key related ones which are provided a `int`s arguments to a method this is sufficient; For Qt 6 there are some additional methods that overcome the "incompatibility" of combining such different `enum`s but those have not been back ported to the Qt 5.15.8 I have - despite what https://bugreports.qt.io/browse/QTBUG-99948 says - so fixes for combining QKey and QKeyModifier have been left out of this PR. 3. Removes a couple of unused variables. 4. Adds our `qsl(...)` wrapper about some raw C-string literals used in a loop. 5. Rewrite part of the qmake project file so the logic and choices are correct. #### Motivation for adding to Mudlet 1. To eliminate the following type of warning when building with a C++20 compiler: "warning: implicit capture of ‘this’ via ‘[=]’ is deprecated in C++20 [-Wdeprecated]" 2. To eliminate the following type of warning when building with a C++20 compiler: * warning: bitwise operation between different enumeration types ‘QFont::Weight’ and ‘QFont::StyleHint’ is deprecated [-Wdeprecated-enum-enum-conversion] * warning: bitwise operation between different enumeration types ‘Qt::TextFlag’ and ‘Qt::AlignmentFlag’ is deprecated [-Wdeprecated-enum-enum-conversion] 3. To eliminate the following type of warning when building with a C++20 compiler: "warning: unused variable ‘pHost’ [-Wunused-variable]" 4. To eliminate the following type of warning when building with a C++20 compiler: "warning: loop variable ‘file’ of type ‘const QString&’ binds to a temporary constructed from type ‘const char* const’ [-Wrange-loop-construct]" 5. The previous logic was (incorrect) IF Qt Major version is less than 5 OR (if Qt Major version is less than 6 AND if Qt Minor version is less than 12)) THEN add `-std=c++20` to `QMAKE_CXXFLAGS` ELSE add `c++2a` to `CONFIG` This is borked because we have already rejected Qt versions less than 5.14 so the logic will **always** end up in the **ELSE** case, and whilst Qt 5.15 is documented as accepting `CONFIG += c++2a` (and `c++2b`) the earliest Qt 6.x version describes `c++2a` as an obsolete alias for `c++20` (in https://doc.qt.io/qt-6.2/qmake-variable-reference.html). #### Other info (issues closed, discussion etc) 3. The code in the `QFont` cases contains errors that have been copied from bogus QFont creation code going back to the very first (well second) commit in the git history. This contained calls of the form `QFont font("Courier New", 10, QFont::Courier)` however even the Qt 4.8 documentation does not list a constructor of that form but instead has: `QFont(const QString & family, int pointSize = -1, int weight = -1, bool italic = false)` the third argument could possibly be `QFont::Normal` (50) or `QFont::Bold` (75) however the value of `QFont::Courier` is `2` but it is for a completely different purpose, that of the font matching strategy ("the font matcher prefers fixed pitch fonts.") but that is not something that can be set as an argument to the font constructor! As it happens the combination of `QFont::Bold | QFont::Serif | QFont::PreferMatch | QFont::PreferAntialias` that was being used numerically equals 75 + 2 + 32 + 128 = 227 - and the scale that Qt actually uses only goes from 0 to 99! 5. This change makes the code match the comments! Overall all the changes in this PR means that https://github.com/Mudlet/Mudlet/pull/7613 is not required after all - at least for Mudlet's own code - though there are still some warnings from the edbee-lib sub-module. --------- Signed-off-by: Stephen Lyons <slysven@virginmedia.com>
2025-02-10 11:59:45 +00:00
#if QT_VERSION >= QT_VERSION_CHECK(6, 7, 0)
connect(port_ssl_tsl, &QCheckBox::checkStateChanged, this, &dlgConnectionProfiles::slot_updateSslTslPort);
connect(autologin_checkBox, &QCheckBox::checkStateChanged, this, &dlgConnectionProfiles::slot_updateAutoConnect);
connect(auto_reconnect, &QCheckBox::checkStateChanged, this, &dlgConnectionProfiles::slot_updateAutoReconnect);
#else
Infrastructure: tidy up naming of SLOT methods and their usage - Part 2 (#6255) When Qt's slot/signal system is used to invoke a method there is some overhead - so it makes sense to ensure developers can spot all such methods (functions). We have tried to do this with a `slot_` prefix to the methods we create but it has not been applied uniformly. This PR (and one or more to follow) is intended to help with this by more rigorously doing so - the names changed herein should all follow a `slot_`*camelCaseMethodName* style. In addition some methods were detected that are not currently used or which are not actually used via the signal/slot system, these have been commented out or have had the prefix removed and the declaration in the relevant header file moved as appropriate. For reference the changes made are: * `TLuaInterpreter::slotDeleteSender(...)` ==> `TLuaInterpreter::slot_deleteSender(...)` * `TLuaInterpreter::slotPurge()` ==> `TLuaInterpreter::slot_purge()` * `cTelnet::handle_socket_signal_connected()` ==> `cTelnet::slot_socketConnected()` * `cTelnet::handle_socket_signal_disconnected()` ==> `cTelnet::slot_socketDisconnected()` * `cTelnet::handle_socket_signal_hostFound(...)` ==> `cTelnet::slot_socketHostFound(...)` * `cTelnet::handle_socket_signal_readyRead()` ==> `cTelnet::slot_socketReadyToBeRead()` * `cTelnet::handle_socket_signal_sslError(...)` ==> `cTelnet::slot_socketSslError(...)` * `cTelnet::setDownloadProgress(...)` ==> `cTelnet::slot_setDownloadProgress(...)` * `dlgComposer::cancel()` ==> `dlgComposer::slot_cancel()` * `dlgComposer::save()` ==> `dlgComposer::slot_save()` * `dlgConnectionProfiles::slot_item_clicked(...)` ==> `dlgConnectionProfiles::slot_itemClicked(...)` * `dlgConnectionProfiles::slot_save_name()` ==> `dlgConnectionProfiles::slot_saveName()` * `dlgConnectionProfiles::slot_update_login(...)` ==> `dlgConnectionProfiles::slot_updateLogin(...)` * `dlgConnectionProfiles::slot_update_name(...)` ==> `dlgConnectionProfiles::slot_updateName(...)` * `dlgConnectionProfiles::slot_update_pass(...)` ==> `dlgConnectionProfiles::slot_updatePassword(...)` * `dlgConnectionProfiles::slot_update_port(...)` ==> `dlgConnectionProfiles::slot_updatePort(...)` * `dlgConnectionProfiles::slot_update_SSL_TSL_port(...)` ==> `dlgConnectionProfiles::slot_updateSslTslPort(...)` * `dlgConnectionProfiles::slot_update_url(...)` ==> `dlgConnectionProfiles::slot_updateUrl(...)` * `dlgMapLabel::pickBgColor()` ==> `dlgMapLabel::slot_pickBgColor()` * `dlgMapLabel::pickFgColor()` ==> `dlgMapLabel::slot_pickFgColor()` * `dlgMapLabel::pickFile()` ==> `dlgMapLabel::slot_pickFile()` * `dlgMapLabel::pickFont()` ==> `dlgMapLabel::slot_pickFont()` * `dlgMapLabel::save()` ==> `dlgMapLabel::slot_save()` * `dlgMapLabel::updateControls()` ==> `dlgMapLabel::slot_updateControls()` * `dlgMapLabel::updateControlsVisibility()` ==> `dlgMapLabel::slot_updateControlsVisibility()` Commented out as not being used: * `cTelnet::handle_socket_signal_error()` ==> `cTelnet::slot_socketError()` * `dlgConnectionProfiles::slot_update_website(...) ==> `dlgConnectionProfiles::slot_updateWebsite(const QString& url)` I have my doubts about whether the `dlgMapLabel::save()`/`dlgMapLabel::slot_save()` method is needed at all, as it might be that the `QDialog::accept()` slot could have been inserted into the `QObject::connect(...)` call directly instead...? Signed-off-by: Stephen Lyons <slysven@virginmedia.com>
2022-08-27 10:47:01 +02:00
connect(port_ssl_tsl, &QCheckBox::stateChanged, this, &dlgConnectionProfiles::slot_updateSslTslPort);
Infrastructure: tidy up naming of SLOT methods and their usage - Part 5 (last) (#6266) When Qt's slot/signal system is used to invoke a method there is some overhead - so it makes sense to ensure developers can spot all such methods (functions). We have tried to do this with a `slot_` prefix to the methods we create but it has not been applied uniformly. This PR is intended to conclude helping with this by more rigorously doing so - the names changed herein should all follow a `slot_`*camelCaseMethodName* style. In addition some methods were detected that are not currently used or which are not actually used via the signal/slot system, these have been commented out or have had the prefix removed and the declaration in the relevant header file moved as appropriate. There were two similar sets of (not prefixed with `slot_`) slots in the Profile preferences class that adjusted the colour settings separately for the main console and the mapper the latter had the same names but with a `2` suffix. To made it more clear I have changed them to include `Map` in their names instead. Also, the NON-slot method: `(void) dlgProfilePreferences::setColor(QPushButton*, QColor&, bool)` has been renamed to: `(void) dlgProfilePreferences::setButtonAndProfileColor(QPushButton*, QColor&, bool)` so that it is clearly distinguishable from built in Qt methods that are also called `setColor` - though which do have different signatures! For reference the changes made are: * `TConsole::slot_stop_all_triggers(...)` ==> `TConsole::slot_stopAllItems(...)` * `dlgConnectionProfiles::slot_copy_profile()` ==> `dlgConnectionProfiles::slot_copyProfile()` * `dlgConnectionProfiles::slot_copy_profilesettings_only()` ==> `dlgConnectionProfiles::slot_copyOnlySettingsOfProfile()` * `dlgConnectionProfiles::slot_deleteprofile_check(...)` ==> `dlgConnectionProfiles::slot_deleteProfileCheck(...)` * `dlgConnectionProfiles::slot_password_deleted(...)` ==> `dlgConnectionProfiles::slot_passwordDeleted(...)` * `dlgConnectionProfiles::slot_password_saved(...) ==> `dlgConnectionProfiles::slot_passwordSaved(...)` * `dlgConnectionProfiles::slot_profile_menu(...)` ==> `dlgConnectionProfiles::slot_profileContextMenu(...)` * `dlgConnectionProfiles::slot_reset_custom_icon()` ==> `dlgConnectionProfiles::slot_resetCustomIcon()` * `dlgConnectionProfiles::slot_set_custom_icon()` ==> `dlgConnectionProfiles::slot_setCustomIcon()` * `dlgConnectionProfiles::slot_set_custom_color()` ==> `dlgConnectionProfiles::slot_setCustomColor()` * `dlgConnectionProfiles::slot_update_autologin(...)` ==> `dlgConnectionProfiles::slot_updateAutoConnect(...)` * `dlgConnectionProfiles::slot_update_autoreconnect(...)` ==> `dlgConnectionProfiles::slot_updateAutoReconnect(...)` * `dlgConnectionProfiles::slot_update_description()` ==> `dlgConnectionProfiles::slot_updateDescription()` * `dlgConnectionProfiles::slot_update_discord_optin(...)` ==> `dlgConnectionProfiles::slot_updateDiscordOptIn(...)` * `dlgProfilePreferences::copyMap()` ==> `dlgProfilePreferences::slot_copyMap()` * `dlgProfilePreferences::downloadMap()` ==> `dlgProfilePreferences::slot_downloadMap()` * `dlgProfilePreferences::hideActionLabel()` ==> `dlgProfilePreferences::slot_hideActionLabel()` * `dlgProfilePreferences::loadMap()` ==> `dlgProfilePreferences::slot_loadMap()` * `dlgProfilePreferences::resetColors()` ==> `dlgProfilePreferences::slot_resetColors()` * `dlgProfilePreferences::resetColors2()` ==> `dlgProfilePreferences::slot_resetMapColors()` * `dlgProfilePreferences::saveMap()` ==> `dlgProfilePreferences::slot_saveMap()` * `dlgProfilePreferences::setBgColor()` ==> `dlgProfilePreferences::slot_setBgColor()` * `dlgProfilePreferences::setBgColor2()` ==> `dlgProfilePreferences::slot_setMapBgColor()` * `dlgProfilePreferences::setColorBlack()` ==> `dlgProfilePreferences::slot_setColorBlack()` * `dlgProfilePreferences::setColorBlack2()` ==> `dlgProfilePreferences::slot_setMapColorBlack()` * `dlgProfilePreferences::setColorBlue()` ==> `dlgProfilePreferences::slot_setColorBlue()` * `dlgProfilePreferences::setColorCyan()` ==> `dlgProfilePreferences::slot_setColorCyan()` * `dlgProfilePreferences::setColorBlue2()` ==> `dlgProfilePreferences::slot_setMapColorBlue()` * `dlgProfilePreferences::setColorCyan2()` ==> `dlgProfilePreferences::slot_setMapColorCyan()` * `dlgProfilePreferences::setColorGreen()` ==> `dlgProfilePreferences::slot_setColorGreen()` * `dlgProfilePreferences::setColorGreen2()` ==> `dlgProfilePreferences::slot_setMapColorGreen()` * `dlgProfilePreferences::setColorLightBlack()` ==> `dlgProfilePreferences::slot_setColorLightBlack()` * `dlgProfilePreferences::setColorLightBlack2()` ==> `dlgProfilePreferences::slot_setMapColorLightBlack()` * `dlgProfilePreferences::setColorLightBlue()` ==> `dlgProfilePreferences::slot_setColorLightBlue()` * `dlgProfilePreferences::setColorLightBlue2()` ==> `dlgProfilePreferences::slot_setMapColorLightBlue()` * `dlgProfilePreferences::setColorLightCyan()` ==> `dlgProfilePreferences::slot_setColorLightCyan()` * `dlgProfilePreferences::setColorLightCyan2()` ==> `dlgProfilePreferences::slot_setMapColorLightCyan()` * `dlgProfilePreferences::setColorLightGreen()` ==> `dlgProfilePreferences::slot_setColorLightGreen()` * `dlgProfilePreferences::setColorLightGreen2()` ==> `dlgProfilePreferences::slot_setMapColorLightGreen()` * `dlgProfilePreferences::setColorLightMagenta()` ==> `dlgProfilePreferences::slot_setColorLightMagenta()` * `dlgProfilePreferences::setColorLightMagenta2()` ==> `dlgProfilePreferences::slot_setMapColorLightMagenta()` * `dlgProfilePreferences::setColorLightRed()` ==> `dlgProfilePreferences::slot_setColorLightRed()` * `dlgProfilePreferences::setColorLightRed2()` ==> `dlgProfilePreferences::slot_setMapColorLightRed()` * `dlgProfilePreferences::setColorLightWhite()` ==> `dlgProfilePreferences::slot_setColorLightWhite()` * `dlgProfilePreferences::setColorLightWhite2()` ==> `dlgProfilePreferences::slot_setMapColorLightWhite()` * `dlgProfilePreferences::setColorLightYellow()` ==> `dlgProfilePreferences::slot_setColorLightYellow()` * `dlgProfilePreferences::setColorLightYellow2()` ==> `dlgProfilePreferences::slot_setMapColorLightYellow()` * `dlgProfilePreferences::setColorMagenta()` ==> `dlgProfilePreferences::slot_setColorMagenta()` * `dlgProfilePreferences::setColorMagenta2()` ==> `dlgProfilePreferences::slot_setMapColorMagenta()` * `dlgProfilePreferences::setColorRed2()` ==> `dlgProfilePreferences::slot_setMapColorRed()` * `dlgProfilePreferences::setColorRed()` ==> `dlgProfilePreferences::slot_setColorRed()` * `dlgProfilePreferences::setColorWhite()` ==> `dlgProfilePreferences::slot_setColorWhite()` * `dlgProfilePreferences::setColorWhite2()` ==> `dlgProfilePreferences::slot_setMapColorWhite()` * `dlgProfilePreferences::setColorYellow()` ==> `dlgProfilePreferences::slot_setColorYellow()` * `dlgProfilePreferences::setColorYellow2()` ==> `dlgProfilePreferences::slot_setMapColorYellow()` * `dlgProfilePreferences::setCommandBgColor()` ==> `dlgProfilePreferences::slot_setCommandBgColor()` * `dlgProfilePreferences::setCommandFgColor()` ==> `dlgProfilePreferences::slot_setCommandFgColor()` * `dlgProfilePreferences::setCommandLineBgColor()` ==> `dlgProfilePreferences::slot_setCommandLineBgColor()` * `dlgProfilePreferences::setCommandLineFgColor()` ==> `dlgProfilePreferences::slot_setCommandLineFgColor()` * `dlgProfilePreferences::setDisplayFont()` ==> `dlgProfilePreferences::slot_setDisplayFont()` * `dlgProfilePreferences::setFgColor()` ==> `dlgProfilePreferences::slot_setFgColor()` * `dlgProfilePreferences::setFgColor2()` ==> `dlgProfilePreferences::slot_setMapExitsColor()` * `dlgProfilePreferences::setFontSize()` ==> `dlgProfilePreferences::slot_setFontSize()` * `dlgProfilePreferences::setMapInfoBackground()` ==> `dlgProfilePreferences::slot_setMapInfoBgColor()` * `dlgProfilePreferences::setRoomBorderColor()` ==> `dlgProfilePreferences::slot_setMapRoomBorderColor()` * `dlgProfilePreferences::slot_script_selected(...)` ==> `dlgProfilePreferences::slot_scriptSelected(...)` * `dlgProfilePreferences::slot_theme_selected(...)` ==> `dlgProfilePreferences::slot_themeSelected(...)` * `dlgRoomSymbol::colorRejected()` ==> `dlgRoomSymbol::slot_colorRejected()` * `dlgRoomSymbol::colorSelected(...)` ==> `dlgRoomSymbol::slot_colorSelected(...)` * `dlgRoomSymbol::currentColorChanged(...)` ==> `dlgRoomSymbol::slot_currentColorChanged(...)` * `dlgRoomSymbol::openColorSelector()` ==> `dlgRoomSymbol::slot_openColorSelector()` * `dlgRoomSymbol::resetColor()` ==> `dlgRoomSymbol::slot_resetColors()` * `dlgRoomSymbol::updatePreview()` ==> `dlgRoomSymbol::slot_updatePreview()` * `dlgTriggerEditor::slot_show_vars()` ==> `dlgTriggerEditor::slot_showVariables()` * `dlgTriggerEditor::slot_var_changed(...)` ==> `dlgTriggerEditor::slot_variableChanged(...)` * `dlgTriggerEditor::slot_var_selected(...)` ==> `dlgTriggerEditor::slot_variableSelected(...)` * `mudlet::slot_check_manual_update()` ==> `mudlet::slot_manualUpdateCheck()` * `mudlet::slot_close_current_profile()` ==> `mudlet::slot_closeCurrentProfile()` * `mudlet::slot_close_profile_requested(...)` ==> `mudlet::slot_closeProfileRequested(...)` * `mudlet::slot_connection_dlg_finished(...)` ==> `mudlet::slot_connectionDialogueFinished(...)` * `mudlet::slot_module_manager()` ==> `mudlet::slot_moduleManager()` * `mudlet::slot_mudlet_discord()` ==> `mudlet::slot_mudletDiscord()` * `mudlet::slot_multi_view(...)` ==> `mudlet::slot_multiView(const bool state)` * `mudlet::slot_package_manager()` ==> `mudlet::slot_packageManager()` * `mudlet::slot_package_exporter()` ==> `mudlet::slot_packageExporter()` * `mudlet::slot_timer_fires()` ==> `mudlet::slot_timerFires()` * `mudlet::slot_toggle_multi_view()` ==> `mudlet::slot_toggleMultiView()` Also the names for these in particular have been changed to make more sense: * `dlgProfilePreferences::slot_chooseProfilesChanged()` ==> `dlgProfilePreferences::slot_chosenProfilesChanged()` * `dlgProfilePreferences::slot_editor_tab_selected(...)` ==> `dlgProfilePreferences::slot_tabChanged(...)` * `dlgProfilePreferences::slot_passwords_location_changed(...)` ==> `dlgProfilePreferences::slot_passwordStorageLocationChanged(...)` * `dlgProfilePreferences::slot_save_and_exit()` ==> `dlgProfilePreferences::slot_saveAndClose()` * `dlgTriggerEditor::slot_toggleHiddenVar(...)` ==> `dlgTriggerEditor::slot_hideVariable(...)` - changed to distinguish it from `slot_toggleHiddenVariables(...)` * `dlgTriggerEditor::slot_item_selected_save(...)` ==> `dlgTriggerEditor::slot_saveSelectedItem(...)` * `mudlet::slot_discord()` ==> `mudlet::slot_profileDiscord()` - changed to distinguish it from `slot_mudletDiscord()` Not currently used and commented out: * `dlgProfilePreferences::setCommandLineFont()` ==> `dlgProfilePreferences::slot_setCommandLineFont()` Signed-off-by: Stephen Lyons <slysven@virginmedia.com>
2022-08-29 14:27:13 +02:00
connect(autologin_checkBox, &QCheckBox::stateChanged, this, &dlgConnectionProfiles::slot_updateAutoConnect);
connect(auto_reconnect, &QCheckBox::stateChanged, this, &dlgConnectionProfiles::slot_updateAutoReconnect);
Fix reduce c++20warnings (#7638) #### Brief overview of PR changes/additions 1. Adds explicit ‘this’ or ‘*this’ capture to lambdas where required (not in static ones). 2. Rephrases some combinations of Qt `enum`s that whilst acceptable for C++17 upset the later one. For the non-key related ones which are provided a `int`s arguments to a method this is sufficient; For Qt 6 there are some additional methods that overcome the "incompatibility" of combining such different `enum`s but those have not been back ported to the Qt 5.15.8 I have - despite what https://bugreports.qt.io/browse/QTBUG-99948 says - so fixes for combining QKey and QKeyModifier have been left out of this PR. 3. Removes a couple of unused variables. 4. Adds our `qsl(...)` wrapper about some raw C-string literals used in a loop. 5. Rewrite part of the qmake project file so the logic and choices are correct. #### Motivation for adding to Mudlet 1. To eliminate the following type of warning when building with a C++20 compiler: "warning: implicit capture of ‘this’ via ‘[=]’ is deprecated in C++20 [-Wdeprecated]" 2. To eliminate the following type of warning when building with a C++20 compiler: * warning: bitwise operation between different enumeration types ‘QFont::Weight’ and ‘QFont::StyleHint’ is deprecated [-Wdeprecated-enum-enum-conversion] * warning: bitwise operation between different enumeration types ‘Qt::TextFlag’ and ‘Qt::AlignmentFlag’ is deprecated [-Wdeprecated-enum-enum-conversion] 3. To eliminate the following type of warning when building with a C++20 compiler: "warning: unused variable ‘pHost’ [-Wunused-variable]" 4. To eliminate the following type of warning when building with a C++20 compiler: "warning: loop variable ‘file’ of type ‘const QString&’ binds to a temporary constructed from type ‘const char* const’ [-Wrange-loop-construct]" 5. The previous logic was (incorrect) IF Qt Major version is less than 5 OR (if Qt Major version is less than 6 AND if Qt Minor version is less than 12)) THEN add `-std=c++20` to `QMAKE_CXXFLAGS` ELSE add `c++2a` to `CONFIG` This is borked because we have already rejected Qt versions less than 5.14 so the logic will **always** end up in the **ELSE** case, and whilst Qt 5.15 is documented as accepting `CONFIG += c++2a` (and `c++2b`) the earliest Qt 6.x version describes `c++2a` as an obsolete alias for `c++20` (in https://doc.qt.io/qt-6.2/qmake-variable-reference.html). #### Other info (issues closed, discussion etc) 3. The code in the `QFont` cases contains errors that have been copied from bogus QFont creation code going back to the very first (well second) commit in the git history. This contained calls of the form `QFont font("Courier New", 10, QFont::Courier)` however even the Qt 4.8 documentation does not list a constructor of that form but instead has: `QFont(const QString & family, int pointSize = -1, int weight = -1, bool italic = false)` the third argument could possibly be `QFont::Normal` (50) or `QFont::Bold` (75) however the value of `QFont::Courier` is `2` but it is for a completely different purpose, that of the font matching strategy ("the font matcher prefers fixed pitch fonts.") but that is not something that can be set as an argument to the font constructor! As it happens the combination of `QFont::Bold | QFont::Serif | QFont::PreferMatch | QFont::PreferAntialias` that was being used numerically equals 75 + 2 + 32 + 128 = 227 - and the scale that Qt actually uses only goes from 0 to 99! 5. This change makes the code match the comments! Overall all the changes in this PR means that https://github.com/Mudlet/Mudlet/pull/7613 is not required after all - at least for Mudlet's own code - though there are still some warnings from the edbee-lib sub-module. --------- Signed-off-by: Stephen Lyons <slysven@virginmedia.com>
2025-02-10 11:59:45 +00:00
#endif
Infrastructure: tidy up naming of SLOT methods and their usage - Part 2 (#6255) When Qt's slot/signal system is used to invoke a method there is some overhead - so it makes sense to ensure developers can spot all such methods (functions). We have tried to do this with a `slot_` prefix to the methods we create but it has not been applied uniformly. This PR (and one or more to follow) is intended to help with this by more rigorously doing so - the names changed herein should all follow a `slot_`*camelCaseMethodName* style. In addition some methods were detected that are not currently used or which are not actually used via the signal/slot system, these have been commented out or have had the prefix removed and the declaration in the relevant header file moved as appropriate. For reference the changes made are: * `TLuaInterpreter::slotDeleteSender(...)` ==> `TLuaInterpreter::slot_deleteSender(...)` * `TLuaInterpreter::slotPurge()` ==> `TLuaInterpreter::slot_purge()` * `cTelnet::handle_socket_signal_connected()` ==> `cTelnet::slot_socketConnected()` * `cTelnet::handle_socket_signal_disconnected()` ==> `cTelnet::slot_socketDisconnected()` * `cTelnet::handle_socket_signal_hostFound(...)` ==> `cTelnet::slot_socketHostFound(...)` * `cTelnet::handle_socket_signal_readyRead()` ==> `cTelnet::slot_socketReadyToBeRead()` * `cTelnet::handle_socket_signal_sslError(...)` ==> `cTelnet::slot_socketSslError(...)` * `cTelnet::setDownloadProgress(...)` ==> `cTelnet::slot_setDownloadProgress(...)` * `dlgComposer::cancel()` ==> `dlgComposer::slot_cancel()` * `dlgComposer::save()` ==> `dlgComposer::slot_save()` * `dlgConnectionProfiles::slot_item_clicked(...)` ==> `dlgConnectionProfiles::slot_itemClicked(...)` * `dlgConnectionProfiles::slot_save_name()` ==> `dlgConnectionProfiles::slot_saveName()` * `dlgConnectionProfiles::slot_update_login(...)` ==> `dlgConnectionProfiles::slot_updateLogin(...)` * `dlgConnectionProfiles::slot_update_name(...)` ==> `dlgConnectionProfiles::slot_updateName(...)` * `dlgConnectionProfiles::slot_update_pass(...)` ==> `dlgConnectionProfiles::slot_updatePassword(...)` * `dlgConnectionProfiles::slot_update_port(...)` ==> `dlgConnectionProfiles::slot_updatePort(...)` * `dlgConnectionProfiles::slot_update_SSL_TSL_port(...)` ==> `dlgConnectionProfiles::slot_updateSslTslPort(...)` * `dlgConnectionProfiles::slot_update_url(...)` ==> `dlgConnectionProfiles::slot_updateUrl(...)` * `dlgMapLabel::pickBgColor()` ==> `dlgMapLabel::slot_pickBgColor()` * `dlgMapLabel::pickFgColor()` ==> `dlgMapLabel::slot_pickFgColor()` * `dlgMapLabel::pickFile()` ==> `dlgMapLabel::slot_pickFile()` * `dlgMapLabel::pickFont()` ==> `dlgMapLabel::slot_pickFont()` * `dlgMapLabel::save()` ==> `dlgMapLabel::slot_save()` * `dlgMapLabel::updateControls()` ==> `dlgMapLabel::slot_updateControls()` * `dlgMapLabel::updateControlsVisibility()` ==> `dlgMapLabel::slot_updateControlsVisibility()` Commented out as not being used: * `cTelnet::handle_socket_signal_error()` ==> `cTelnet::slot_socketError()` * `dlgConnectionProfiles::slot_update_website(...) ==> `dlgConnectionProfiles::slot_updateWebsite(const QString& url)` I have my doubts about whether the `dlgMapLabel::save()`/`dlgMapLabel::slot_save()` method is needed at all, as it might be that the `QDialog::accept()` slot could have been inserted into the `QObject::connect(...)` call directly instead...? Signed-off-by: Stephen Lyons <slysven@virginmedia.com>
2022-08-27 10:47:01 +02:00
connect(login_entry, &QLineEdit::textEdited, this, &dlgConnectionProfiles::slot_updateLogin);
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 06:44:38 -04:00
// Use textChanged with timer debouncing to avoid saving on every keystroke
connect(character_password_entry, &QLineEdit::textChanged, this, &dlgConnectionProfiles::slot_passwordTextChanged);
// Listen for password migration completion to refresh the form
connect(mudlet::self(), &mudlet::signal_passwordsMigratedToSecure, this, [this]() {
// Refresh the current profile's password field after migration; this
// is not the user picking a game so keep the welcome message up
mProgrammaticProfileSelection = true;
slot_itemClicked(listWidget_profiles->currentItem());
mProgrammaticProfileSelection = false;
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 06:44:38 -04:00
});
2025-09-13 14:17:13 +07:00
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 06:44:38 -04:00
// Listen for character password migration completion to refresh the form
connect(mudlet::self(), &mudlet::signal_characterPasswordsMigrated, this, [this]() {
// Refresh the current profile's password field after migration; this
// is not the user picking a game so keep the welcome message up
mProgrammaticProfileSelection = true;
slot_itemClicked(listWidget_profiles->currentItem());
mProgrammaticProfileSelection = false;
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 06:44:38 -04:00
});
Infrastructure: tidy up naming of SLOT methods and their usage - Part 5 (last) (#6266) When Qt's slot/signal system is used to invoke a method there is some overhead - so it makes sense to ensure developers can spot all such methods (functions). We have tried to do this with a `slot_` prefix to the methods we create but it has not been applied uniformly. This PR is intended to conclude helping with this by more rigorously doing so - the names changed herein should all follow a `slot_`*camelCaseMethodName* style. In addition some methods were detected that are not currently used or which are not actually used via the signal/slot system, these have been commented out or have had the prefix removed and the declaration in the relevant header file moved as appropriate. There were two similar sets of (not prefixed with `slot_`) slots in the Profile preferences class that adjusted the colour settings separately for the main console and the mapper the latter had the same names but with a `2` suffix. To made it more clear I have changed them to include `Map` in their names instead. Also, the NON-slot method: `(void) dlgProfilePreferences::setColor(QPushButton*, QColor&, bool)` has been renamed to: `(void) dlgProfilePreferences::setButtonAndProfileColor(QPushButton*, QColor&, bool)` so that it is clearly distinguishable from built in Qt methods that are also called `setColor` - though which do have different signatures! For reference the changes made are: * `TConsole::slot_stop_all_triggers(...)` ==> `TConsole::slot_stopAllItems(...)` * `dlgConnectionProfiles::slot_copy_profile()` ==> `dlgConnectionProfiles::slot_copyProfile()` * `dlgConnectionProfiles::slot_copy_profilesettings_only()` ==> `dlgConnectionProfiles::slot_copyOnlySettingsOfProfile()` * `dlgConnectionProfiles::slot_deleteprofile_check(...)` ==> `dlgConnectionProfiles::slot_deleteProfileCheck(...)` * `dlgConnectionProfiles::slot_password_deleted(...)` ==> `dlgConnectionProfiles::slot_passwordDeleted(...)` * `dlgConnectionProfiles::slot_password_saved(...) ==> `dlgConnectionProfiles::slot_passwordSaved(...)` * `dlgConnectionProfiles::slot_profile_menu(...)` ==> `dlgConnectionProfiles::slot_profileContextMenu(...)` * `dlgConnectionProfiles::slot_reset_custom_icon()` ==> `dlgConnectionProfiles::slot_resetCustomIcon()` * `dlgConnectionProfiles::slot_set_custom_icon()` ==> `dlgConnectionProfiles::slot_setCustomIcon()` * `dlgConnectionProfiles::slot_set_custom_color()` ==> `dlgConnectionProfiles::slot_setCustomColor()` * `dlgConnectionProfiles::slot_update_autologin(...)` ==> `dlgConnectionProfiles::slot_updateAutoConnect(...)` * `dlgConnectionProfiles::slot_update_autoreconnect(...)` ==> `dlgConnectionProfiles::slot_updateAutoReconnect(...)` * `dlgConnectionProfiles::slot_update_description()` ==> `dlgConnectionProfiles::slot_updateDescription()` * `dlgConnectionProfiles::slot_update_discord_optin(...)` ==> `dlgConnectionProfiles::slot_updateDiscordOptIn(...)` * `dlgProfilePreferences::copyMap()` ==> `dlgProfilePreferences::slot_copyMap()` * `dlgProfilePreferences::downloadMap()` ==> `dlgProfilePreferences::slot_downloadMap()` * `dlgProfilePreferences::hideActionLabel()` ==> `dlgProfilePreferences::slot_hideActionLabel()` * `dlgProfilePreferences::loadMap()` ==> `dlgProfilePreferences::slot_loadMap()` * `dlgProfilePreferences::resetColors()` ==> `dlgProfilePreferences::slot_resetColors()` * `dlgProfilePreferences::resetColors2()` ==> `dlgProfilePreferences::slot_resetMapColors()` * `dlgProfilePreferences::saveMap()` ==> `dlgProfilePreferences::slot_saveMap()` * `dlgProfilePreferences::setBgColor()` ==> `dlgProfilePreferences::slot_setBgColor()` * `dlgProfilePreferences::setBgColor2()` ==> `dlgProfilePreferences::slot_setMapBgColor()` * `dlgProfilePreferences::setColorBlack()` ==> `dlgProfilePreferences::slot_setColorBlack()` * `dlgProfilePreferences::setColorBlack2()` ==> `dlgProfilePreferences::slot_setMapColorBlack()` * `dlgProfilePreferences::setColorBlue()` ==> `dlgProfilePreferences::slot_setColorBlue()` * `dlgProfilePreferences::setColorCyan()` ==> `dlgProfilePreferences::slot_setColorCyan()` * `dlgProfilePreferences::setColorBlue2()` ==> `dlgProfilePreferences::slot_setMapColorBlue()` * `dlgProfilePreferences::setColorCyan2()` ==> `dlgProfilePreferences::slot_setMapColorCyan()` * `dlgProfilePreferences::setColorGreen()` ==> `dlgProfilePreferences::slot_setColorGreen()` * `dlgProfilePreferences::setColorGreen2()` ==> `dlgProfilePreferences::slot_setMapColorGreen()` * `dlgProfilePreferences::setColorLightBlack()` ==> `dlgProfilePreferences::slot_setColorLightBlack()` * `dlgProfilePreferences::setColorLightBlack2()` ==> `dlgProfilePreferences::slot_setMapColorLightBlack()` * `dlgProfilePreferences::setColorLightBlue()` ==> `dlgProfilePreferences::slot_setColorLightBlue()` * `dlgProfilePreferences::setColorLightBlue2()` ==> `dlgProfilePreferences::slot_setMapColorLightBlue()` * `dlgProfilePreferences::setColorLightCyan()` ==> `dlgProfilePreferences::slot_setColorLightCyan()` * `dlgProfilePreferences::setColorLightCyan2()` ==> `dlgProfilePreferences::slot_setMapColorLightCyan()` * `dlgProfilePreferences::setColorLightGreen()` ==> `dlgProfilePreferences::slot_setColorLightGreen()` * `dlgProfilePreferences::setColorLightGreen2()` ==> `dlgProfilePreferences::slot_setMapColorLightGreen()` * `dlgProfilePreferences::setColorLightMagenta()` ==> `dlgProfilePreferences::slot_setColorLightMagenta()` * `dlgProfilePreferences::setColorLightMagenta2()` ==> `dlgProfilePreferences::slot_setMapColorLightMagenta()` * `dlgProfilePreferences::setColorLightRed()` ==> `dlgProfilePreferences::slot_setColorLightRed()` * `dlgProfilePreferences::setColorLightRed2()` ==> `dlgProfilePreferences::slot_setMapColorLightRed()` * `dlgProfilePreferences::setColorLightWhite()` ==> `dlgProfilePreferences::slot_setColorLightWhite()` * `dlgProfilePreferences::setColorLightWhite2()` ==> `dlgProfilePreferences::slot_setMapColorLightWhite()` * `dlgProfilePreferences::setColorLightYellow()` ==> `dlgProfilePreferences::slot_setColorLightYellow()` * `dlgProfilePreferences::setColorLightYellow2()` ==> `dlgProfilePreferences::slot_setMapColorLightYellow()` * `dlgProfilePreferences::setColorMagenta()` ==> `dlgProfilePreferences::slot_setColorMagenta()` * `dlgProfilePreferences::setColorMagenta2()` ==> `dlgProfilePreferences::slot_setMapColorMagenta()` * `dlgProfilePreferences::setColorRed2()` ==> `dlgProfilePreferences::slot_setMapColorRed()` * `dlgProfilePreferences::setColorRed()` ==> `dlgProfilePreferences::slot_setColorRed()` * `dlgProfilePreferences::setColorWhite()` ==> `dlgProfilePreferences::slot_setColorWhite()` * `dlgProfilePreferences::setColorWhite2()` ==> `dlgProfilePreferences::slot_setMapColorWhite()` * `dlgProfilePreferences::setColorYellow()` ==> `dlgProfilePreferences::slot_setColorYellow()` * `dlgProfilePreferences::setColorYellow2()` ==> `dlgProfilePreferences::slot_setMapColorYellow()` * `dlgProfilePreferences::setCommandBgColor()` ==> `dlgProfilePreferences::slot_setCommandBgColor()` * `dlgProfilePreferences::setCommandFgColor()` ==> `dlgProfilePreferences::slot_setCommandFgColor()` * `dlgProfilePreferences::setCommandLineBgColor()` ==> `dlgProfilePreferences::slot_setCommandLineBgColor()` * `dlgProfilePreferences::setCommandLineFgColor()` ==> `dlgProfilePreferences::slot_setCommandLineFgColor()` * `dlgProfilePreferences::setDisplayFont()` ==> `dlgProfilePreferences::slot_setDisplayFont()` * `dlgProfilePreferences::setFgColor()` ==> `dlgProfilePreferences::slot_setFgColor()` * `dlgProfilePreferences::setFgColor2()` ==> `dlgProfilePreferences::slot_setMapExitsColor()` * `dlgProfilePreferences::setFontSize()` ==> `dlgProfilePreferences::slot_setFontSize()` * `dlgProfilePreferences::setMapInfoBackground()` ==> `dlgProfilePreferences::slot_setMapInfoBgColor()` * `dlgProfilePreferences::setRoomBorderColor()` ==> `dlgProfilePreferences::slot_setMapRoomBorderColor()` * `dlgProfilePreferences::slot_script_selected(...)` ==> `dlgProfilePreferences::slot_scriptSelected(...)` * `dlgProfilePreferences::slot_theme_selected(...)` ==> `dlgProfilePreferences::slot_themeSelected(...)` * `dlgRoomSymbol::colorRejected()` ==> `dlgRoomSymbol::slot_colorRejected()` * `dlgRoomSymbol::colorSelected(...)` ==> `dlgRoomSymbol::slot_colorSelected(...)` * `dlgRoomSymbol::currentColorChanged(...)` ==> `dlgRoomSymbol::slot_currentColorChanged(...)` * `dlgRoomSymbol::openColorSelector()` ==> `dlgRoomSymbol::slot_openColorSelector()` * `dlgRoomSymbol::resetColor()` ==> `dlgRoomSymbol::slot_resetColors()` * `dlgRoomSymbol::updatePreview()` ==> `dlgRoomSymbol::slot_updatePreview()` * `dlgTriggerEditor::slot_show_vars()` ==> `dlgTriggerEditor::slot_showVariables()` * `dlgTriggerEditor::slot_var_changed(...)` ==> `dlgTriggerEditor::slot_variableChanged(...)` * `dlgTriggerEditor::slot_var_selected(...)` ==> `dlgTriggerEditor::slot_variableSelected(...)` * `mudlet::slot_check_manual_update()` ==> `mudlet::slot_manualUpdateCheck()` * `mudlet::slot_close_current_profile()` ==> `mudlet::slot_closeCurrentProfile()` * `mudlet::slot_close_profile_requested(...)` ==> `mudlet::slot_closeProfileRequested(...)` * `mudlet::slot_connection_dlg_finished(...)` ==> `mudlet::slot_connectionDialogueFinished(...)` * `mudlet::slot_module_manager()` ==> `mudlet::slot_moduleManager()` * `mudlet::slot_mudlet_discord()` ==> `mudlet::slot_mudletDiscord()` * `mudlet::slot_multi_view(...)` ==> `mudlet::slot_multiView(const bool state)` * `mudlet::slot_package_manager()` ==> `mudlet::slot_packageManager()` * `mudlet::slot_package_exporter()` ==> `mudlet::slot_packageExporter()` * `mudlet::slot_timer_fires()` ==> `mudlet::slot_timerFires()` * `mudlet::slot_toggle_multi_view()` ==> `mudlet::slot_toggleMultiView()` Also the names for these in particular have been changed to make more sense: * `dlgProfilePreferences::slot_chooseProfilesChanged()` ==> `dlgProfilePreferences::slot_chosenProfilesChanged()` * `dlgProfilePreferences::slot_editor_tab_selected(...)` ==> `dlgProfilePreferences::slot_tabChanged(...)` * `dlgProfilePreferences::slot_passwords_location_changed(...)` ==> `dlgProfilePreferences::slot_passwordStorageLocationChanged(...)` * `dlgProfilePreferences::slot_save_and_exit()` ==> `dlgProfilePreferences::slot_saveAndClose()` * `dlgTriggerEditor::slot_toggleHiddenVar(...)` ==> `dlgTriggerEditor::slot_hideVariable(...)` - changed to distinguish it from `slot_toggleHiddenVariables(...)` * `dlgTriggerEditor::slot_item_selected_save(...)` ==> `dlgTriggerEditor::slot_saveSelectedItem(...)` * `mudlet::slot_discord()` ==> `mudlet::slot_profileDiscord()` - changed to distinguish it from `slot_mudletDiscord()` Not currently used and commented out: * `dlgProfilePreferences::setCommandLineFont()` ==> `dlgProfilePreferences::slot_setCommandLineFont()` Signed-off-by: Stephen Lyons <slysven@virginmedia.com>
2022-08-29 14:27:13 +02:00
connect(mud_description_textedit, &QPlainTextEdit::textChanged, this, &dlgConnectionProfiles::slot_updateDescription);
connect(listWidget_profiles, &QListWidget::currentItemChanged, this, &dlgConnectionProfiles::slot_itemClicked);
// clicking the item that is already current (a profile gets pre-selected
// before the dialog is shown) does not change the current item, so it
// still needs to reveal the connection details on a fresh install
connect(listWidget_profiles, &QListWidget::itemClicked, this, &dlgConnectionProfiles::revealConnectionDetails);
connect(listWidget_profiles, &QListWidget::itemDoubleClicked, this, &dlgConnectionProfiles::accept);
2010-03-15 09:37:16 +01:00
// website_entry atm is only a label
Infrastructure: tidy up naming of SLOT methods and their usage - Part 2 (#6255) When Qt's slot/signal system is used to invoke a method there is some overhead - so it makes sense to ensure developers can spot all such methods (functions). We have tried to do this with a `slot_` prefix to the methods we create but it has not been applied uniformly. This PR (and one or more to follow) is intended to help with this by more rigorously doing so - the names changed herein should all follow a `slot_`*camelCaseMethodName* style. In addition some methods were detected that are not currently used or which are not actually used via the signal/slot system, these have been commented out or have had the prefix removed and the declaration in the relevant header file moved as appropriate. For reference the changes made are: * `TLuaInterpreter::slotDeleteSender(...)` ==> `TLuaInterpreter::slot_deleteSender(...)` * `TLuaInterpreter::slotPurge()` ==> `TLuaInterpreter::slot_purge()` * `cTelnet::handle_socket_signal_connected()` ==> `cTelnet::slot_socketConnected()` * `cTelnet::handle_socket_signal_disconnected()` ==> `cTelnet::slot_socketDisconnected()` * `cTelnet::handle_socket_signal_hostFound(...)` ==> `cTelnet::slot_socketHostFound(...)` * `cTelnet::handle_socket_signal_readyRead()` ==> `cTelnet::slot_socketReadyToBeRead()` * `cTelnet::handle_socket_signal_sslError(...)` ==> `cTelnet::slot_socketSslError(...)` * `cTelnet::setDownloadProgress(...)` ==> `cTelnet::slot_setDownloadProgress(...)` * `dlgComposer::cancel()` ==> `dlgComposer::slot_cancel()` * `dlgComposer::save()` ==> `dlgComposer::slot_save()` * `dlgConnectionProfiles::slot_item_clicked(...)` ==> `dlgConnectionProfiles::slot_itemClicked(...)` * `dlgConnectionProfiles::slot_save_name()` ==> `dlgConnectionProfiles::slot_saveName()` * `dlgConnectionProfiles::slot_update_login(...)` ==> `dlgConnectionProfiles::slot_updateLogin(...)` * `dlgConnectionProfiles::slot_update_name(...)` ==> `dlgConnectionProfiles::slot_updateName(...)` * `dlgConnectionProfiles::slot_update_pass(...)` ==> `dlgConnectionProfiles::slot_updatePassword(...)` * `dlgConnectionProfiles::slot_update_port(...)` ==> `dlgConnectionProfiles::slot_updatePort(...)` * `dlgConnectionProfiles::slot_update_SSL_TSL_port(...)` ==> `dlgConnectionProfiles::slot_updateSslTslPort(...)` * `dlgConnectionProfiles::slot_update_url(...)` ==> `dlgConnectionProfiles::slot_updateUrl(...)` * `dlgMapLabel::pickBgColor()` ==> `dlgMapLabel::slot_pickBgColor()` * `dlgMapLabel::pickFgColor()` ==> `dlgMapLabel::slot_pickFgColor()` * `dlgMapLabel::pickFile()` ==> `dlgMapLabel::slot_pickFile()` * `dlgMapLabel::pickFont()` ==> `dlgMapLabel::slot_pickFont()` * `dlgMapLabel::save()` ==> `dlgMapLabel::slot_save()` * `dlgMapLabel::updateControls()` ==> `dlgMapLabel::slot_updateControls()` * `dlgMapLabel::updateControlsVisibility()` ==> `dlgMapLabel::slot_updateControlsVisibility()` Commented out as not being used: * `cTelnet::handle_socket_signal_error()` ==> `cTelnet::slot_socketError()` * `dlgConnectionProfiles::slot_update_website(...) ==> `dlgConnectionProfiles::slot_updateWebsite(const QString& url)` I have my doubts about whether the `dlgMapLabel::save()`/`dlgMapLabel::slot_save()` method is needed at all, as it might be that the `QDialog::accept()` slot could have been inserted into the `QObject::connect(...)` call directly instead...? Signed-off-by: Stephen Lyons <slysven@virginmedia.com>
2022-08-27 10:47:01 +02:00
//connect(website_entry, SIGNAL(textEdited(const QString)), this, SLOT(slot_updateWebsite(const QString)));
2010-03-15 09:37:16 +01:00
2021-02-03 19:59:35 +00:00
clearNotificationArea();
2010-03-15 09:37:16 +01:00
2019-01-06 06:29:16 -05:00
#if !defined(QT_NO_SSL)
if (QSslSocket::supportsSsl()) {
port_ssl_tsl->setEnabled(true);
} else {
2019-01-06 06:29:16 -05:00
#endif
port_ssl_tsl->setEnabled(false);
#if !defined(QT_NO_SSL)
2019-01-06 06:29:16 -05:00
}
#endif
2019-01-06 06:29:16 -05:00
mReadOnlyPalette.setColor(QPalette::Base, QColor(125, 125, 125, 25));
mOKPalette.setColor(QPalette::Base, QColor(150, 255, 150, 50));
mErrorPalette.setColor(QPalette::Base, QColor(255, 150, 150, 50));
2010-03-15 09:37:16 +01:00
listWidget_profiles->setViewMode(QListView::IconMode);
btn_load_enabled_accessDesc = tr("Click to load but not connect the selected profile.");
btn_connect_enabled_accessDesc = tr("Click to load and connect the selected profile.");
btn_connOrLoad_disabled_accessDesc = tr("Need to have a valid profile name, game server address and port before this button can be enabled.");
item_profile_accessName = tr("Game name: %1");
//: Some text to speech engines will spell out initials like MUD so stick to lower case if that is a better option
item_profile_accessDesc = tr("Button to select a mud game to play, double-click it to connect and start playing it.");
Revise: select profiles by typing the name, keep profile icons (#4440) Restore the text display of the profile name. This is so that the keyboard selection of profiles beginning with a particular letter works again. This will close issue #4283 . This also sets it to the minimum size trying to hide it by setting the font size to 1 - however that still show something (and a font size of zero is NOT valid). Manipulating the QPalette of the QListWidget does not work well enough in the presence of Styles or StyleSheets to be a viable means of hiding the text either. Also in the `dlgConnection` class: * refactor out some highly repetitive code that sets up the predefined Mud icons. * remove warnings and adopts a change introduced into Qt 5.15 for the QLabel::pixmap() which now requires a Qt::ReturnByValue argument to prevent an obsolete code warning and to force a QPixmap to be returned by value and not reference... * remove a warning about: QListWidget::setItemSelected(QListWidgetItem*,bool) being deprecated and switches to the recommended: QListWidgetItem::setSelected(bool) Edit: the last item above may have become redundant as further commits amalgamated into this PR may have taken out these lines anyhow! Change the custom icon generator so that there is NOT a dependency on the position in the list for the colours used. This means that the same name will always have the same colours - which will help the user to pick out a particular profile as it will always look the same no matter if other profiles are added or removed. Remove the colour gradient in the custom icon as it is not thought to be useful nowadays. Refactor out the colour icon generator to a single method that is called from more than one place. BugFix: restore odd original way of accessing some QPixmaps This was causing failures in some CI platforms using Qt 5.14. or older. Revise: really frob the custom icon generator This is to try and produce as varied but still readable icons when they are generated entirely on the basis of the MUD name - in a reproducible (though Qt do NOT guarantee repeatable qHash() functionality across Qt versions - so they may not stay *that* constant) manner. Refrob: use a table of fixed colours Using only 15 hues but with 3 different saturation level plus 5 grey-scale one should give enough different icons but not generate them too close to each other for those with normal colour vision... Signed-off-by: Stephen Lyons <slysven@virginmedia.com> Co-authored-by: Kebap <kebap_spam@gmx.net>
2020-12-11 17:53:28 +00:00
// Set up some initial black/white/greys:
mCustomIconColors = {{QColor(0, 0, 0)}, {QColor(63, 63, 63)}, {QColor(128, 128, 128)}, {QColor(192, 192, 192)}, {QColor(255, 255, 255)}};
Revise: select profiles by typing the name, keep profile icons (#4440) Restore the text display of the profile name. This is so that the keyboard selection of profiles beginning with a particular letter works again. This will close issue #4283 . This also sets it to the minimum size trying to hide it by setting the font size to 1 - however that still show something (and a font size of zero is NOT valid). Manipulating the QPalette of the QListWidget does not work well enough in the presence of Styles or StyleSheets to be a viable means of hiding the text either. Also in the `dlgConnection` class: * refactor out some highly repetitive code that sets up the predefined Mud icons. * remove warnings and adopts a change introduced into Qt 5.15 for the QLabel::pixmap() which now requires a Qt::ReturnByValue argument to prevent an obsolete code warning and to force a QPixmap to be returned by value and not reference... * remove a warning about: QListWidget::setItemSelected(QListWidgetItem*,bool) being deprecated and switches to the recommended: QListWidgetItem::setSelected(bool) Edit: the last item above may have become redundant as further commits amalgamated into this PR may have taken out these lines anyhow! Change the custom icon generator so that there is NOT a dependency on the position in the list for the colours used. This means that the same name will always have the same colours - which will help the user to pick out a particular profile as it will always look the same no matter if other profiles are added or removed. Remove the colour gradient in the custom icon as it is not thought to be useful nowadays. Refactor out the colour icon generator to a single method that is called from more than one place. BugFix: restore odd original way of accessing some QPixmaps This was causing failures in some CI platforms using Qt 5.14. or older. Revise: really frob the custom icon generator This is to try and produce as varied but still readable icons when they are generated entirely on the basis of the MUD name - in a reproducible (though Qt do NOT guarantee repeatable qHash() functionality across Qt versions - so they may not stay *that* constant) manner. Refrob: use a table of fixed colours Using only 15 hues but with 3 different saturation level plus 5 grey-scale one should give enough different icons but not generate them too close to each other for those with normal colour vision... Signed-off-by: Stephen Lyons <slysven@virginmedia.com> Co-authored-by: Kebap <kebap_spam@gmx.net>
2020-12-11 17:53:28 +00:00
// Add some color ones with evenly spaced hue
for (quint16 i = 0; i < 360; i += 24) {
mCustomIconColors.append(QColor::fromHsv(i, 255, 255));
mCustomIconColors.append(QColor::fromHsv(i, 192, 255));
mCustomIconColors.append(QColor::fromHsv(i, 128, 255));
Revise: select profiles by typing the name, keep profile icons (#4440) Restore the text display of the profile name. This is so that the keyboard selection of profiles beginning with a particular letter works again. This will close issue #4283 . This also sets it to the minimum size trying to hide it by setting the font size to 1 - however that still show something (and a font size of zero is NOT valid). Manipulating the QPalette of the QListWidget does not work well enough in the presence of Styles or StyleSheets to be a viable means of hiding the text either. Also in the `dlgConnection` class: * refactor out some highly repetitive code that sets up the predefined Mud icons. * remove warnings and adopts a change introduced into Qt 5.15 for the QLabel::pixmap() which now requires a Qt::ReturnByValue argument to prevent an obsolete code warning and to force a QPixmap to be returned by value and not reference... * remove a warning about: QListWidget::setItemSelected(QListWidgetItem*,bool) being deprecated and switches to the recommended: QListWidgetItem::setSelected(bool) Edit: the last item above may have become redundant as further commits amalgamated into this PR may have taken out these lines anyhow! Change the custom icon generator so that there is NOT a dependency on the position in the list for the colours used. This means that the same name will always have the same colours - which will help the user to pick out a particular profile as it will always look the same no matter if other profiles are added or removed. Remove the colour gradient in the custom icon as it is not thought to be useful nowadays. Refactor out the colour icon generator to a single method that is called from more than one place. BugFix: restore odd original way of accessing some QPixmaps This was causing failures in some CI platforms using Qt 5.14. or older. Revise: really frob the custom icon generator This is to try and produce as varied but still readable icons when they are generated entirely on the basis of the MUD name - in a reproducible (though Qt do NOT guarantee repeatable qHash() functionality across Qt versions - so they may not stay *that* constant) manner. Refrob: use a table of fixed colours Using only 15 hues but with 3 different saturation level plus 5 grey-scale one should give enough different icons but not generate them too close to each other for those with normal colour vision... Signed-off-by: Stephen Lyons <slysven@virginmedia.com> Co-authored-by: Kebap <kebap_spam@gmx.net>
2020-12-11 17:53:28 +00:00
}
2021-02-03 19:59:35 +00:00
mSearchTextTimer.setInterval(1s);
mSearchTextTimer.setSingleShot(true);
QCoreApplication::instance()->installEventFilter(this);
connect(&mSearchTextTimer, &QTimer::timeout, this, &dlgConnectionProfiles::slot_reenableAllProfileItems);
profile_history->view()->setTextElideMode(Qt::ElideNone);
2021-02-03 19:59:35 +00:00
}
dlgConnectionProfiles::~dlgConnectionProfiles()
{
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
// ~QDialog hides the dialog once this destructor is done, and the profile
// name field reacts to losing the focus by emitting editingFinished() into
// slot_saveName() when this object is no longer a valid receiver (#9574)
utils::disconnectChildSignals(this);
if (mPasswordSaveTimer) {
mPasswordSaveTimer->stop();
}
mPendingPasswordSaveProfile.clear();
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 06:44:38 -04:00
// Clear any pending operation flags
mKeychainOperationInProgress = false;
mPendingProfileLoad.clear();
2025-09-13 14:17:13 +07:00
// Check if QCoreApplication is still valid during shutdown
if (QCoreApplication::instance()) {
QCoreApplication::instance()->removeEventFilter(this);
}
}
2010-03-15 09:37:16 +01:00
// Restores the widgets that the first-launch tutorial invitation hides, so
// every path out of the invitation (Skip button, New profile) leaves the
// dialog in its regular state:
void dlgConnectionProfiles::dismissTutorialInvitation()
{
mTutorialDismissed = true;
if (!widget_topLeft->isHidden()) {
// the invitation is not up, so there is nothing to restore - and the
// resize below would make the dialog jump in size for no reason
return;
}
widget_topLeft->show();
welcome_message->hide();
tabWidget_connectionInfo->show();
informationArea->show();
mpSkipToGamesButton->hide();
connect_button->show();
offline_button->show();
// The invitation shrank the dialog to fit its short message; size the
// restored full interface from its own layout instead:
resize(sizeHint().expandedTo(minimumSize()));
}
void dlgConnectionProfiles::slot_skipToGamesList()
{
dismissTutorialInvitation();
const auto items = findData(*listWidget_profiles, qsl("Mudlet Tutorial"), csmNameRole);
if (!items.isEmpty()) {
listWidget_profiles->setCurrentItem(items.first());
}
}
// the dialog can be accepted by pressing Enter on an qlineedit; this is a safeguard against it
// accepting invalid data
void dlgConnectionProfiles::accept()
{
2019-01-06 06:29:16 -05:00
if (validName && validUrl && validPort) {
setVisible(false);
// This is needed to make the above take effect as fast as possible:
qApp->processEvents();
2025-09-13 14:17:13 +07:00
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 06:44:38 -04:00
// Check if keychain authentication is pending - if so, wait for it
ensurePasswordLoadedThenConnect(true);
}
}
void dlgConnectionProfiles::slot_load()
{
setVisible(false);
// This is needed to make the above take effect as fast as possible:
qApp->processEvents();
2025-09-13 14:17:13 +07:00
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 06:44:38 -04:00
// Check if keychain authentication is pending - if so, wait for it
ensurePasswordLoadedThenConnect(false);
}
void dlgConnectionProfiles::ensurePasswordLoadedThenConnect(bool alsoConnect)
{
const QString profile_name = profile_name_entry->text().trimmed();
2025-09-13 14:17:13 +07:00
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 06:44:38 -04:00
if (profile_name.isEmpty()) {
QDialog::accept();
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 06:44:38 -04:00
return;
}
2025-09-13 14:17:13 +07:00
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 06:44:38 -04:00
// Check if we have any pending keychain operations for this profile
if (hasPendingKeychainOperation(profile_name)) {
// Queue the profile loading until keychain completes
mPendingConnect = alsoConnect;
mPendingProfileLoad = profile_name;
return; // Will be handled by keychain callback
}
2025-09-13 14:17:13 +07:00
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 06:44:38 -04:00
// No pending keychain operations, proceed immediately
loadProfile(alsoConnect);
QDialog::accept();
}
bool dlgConnectionProfiles::hasPendingKeychainOperation(const QString& profile_name) const
{
Q_UNUSED(profile_name)
// Simply check if we have a keychain operation in progress
return mKeychainOperationInProgress;
}
Infrastructure: tidy up naming of SLOT methods and their usage - Part 5 (last) (#6266) When Qt's slot/signal system is used to invoke a method there is some overhead - so it makes sense to ensure developers can spot all such methods (functions). We have tried to do this with a `slot_` prefix to the methods we create but it has not been applied uniformly. This PR is intended to conclude helping with this by more rigorously doing so - the names changed herein should all follow a `slot_`*camelCaseMethodName* style. In addition some methods were detected that are not currently used or which are not actually used via the signal/slot system, these have been commented out or have had the prefix removed and the declaration in the relevant header file moved as appropriate. There were two similar sets of (not prefixed with `slot_`) slots in the Profile preferences class that adjusted the colour settings separately for the main console and the mapper the latter had the same names but with a `2` suffix. To made it more clear I have changed them to include `Map` in their names instead. Also, the NON-slot method: `(void) dlgProfilePreferences::setColor(QPushButton*, QColor&, bool)` has been renamed to: `(void) dlgProfilePreferences::setButtonAndProfileColor(QPushButton*, QColor&, bool)` so that it is clearly distinguishable from built in Qt methods that are also called `setColor` - though which do have different signatures! For reference the changes made are: * `TConsole::slot_stop_all_triggers(...)` ==> `TConsole::slot_stopAllItems(...)` * `dlgConnectionProfiles::slot_copy_profile()` ==> `dlgConnectionProfiles::slot_copyProfile()` * `dlgConnectionProfiles::slot_copy_profilesettings_only()` ==> `dlgConnectionProfiles::slot_copyOnlySettingsOfProfile()` * `dlgConnectionProfiles::slot_deleteprofile_check(...)` ==> `dlgConnectionProfiles::slot_deleteProfileCheck(...)` * `dlgConnectionProfiles::slot_password_deleted(...)` ==> `dlgConnectionProfiles::slot_passwordDeleted(...)` * `dlgConnectionProfiles::slot_password_saved(...) ==> `dlgConnectionProfiles::slot_passwordSaved(...)` * `dlgConnectionProfiles::slot_profile_menu(...)` ==> `dlgConnectionProfiles::slot_profileContextMenu(...)` * `dlgConnectionProfiles::slot_reset_custom_icon()` ==> `dlgConnectionProfiles::slot_resetCustomIcon()` * `dlgConnectionProfiles::slot_set_custom_icon()` ==> `dlgConnectionProfiles::slot_setCustomIcon()` * `dlgConnectionProfiles::slot_set_custom_color()` ==> `dlgConnectionProfiles::slot_setCustomColor()` * `dlgConnectionProfiles::slot_update_autologin(...)` ==> `dlgConnectionProfiles::slot_updateAutoConnect(...)` * `dlgConnectionProfiles::slot_update_autoreconnect(...)` ==> `dlgConnectionProfiles::slot_updateAutoReconnect(...)` * `dlgConnectionProfiles::slot_update_description()` ==> `dlgConnectionProfiles::slot_updateDescription()` * `dlgConnectionProfiles::slot_update_discord_optin(...)` ==> `dlgConnectionProfiles::slot_updateDiscordOptIn(...)` * `dlgProfilePreferences::copyMap()` ==> `dlgProfilePreferences::slot_copyMap()` * `dlgProfilePreferences::downloadMap()` ==> `dlgProfilePreferences::slot_downloadMap()` * `dlgProfilePreferences::hideActionLabel()` ==> `dlgProfilePreferences::slot_hideActionLabel()` * `dlgProfilePreferences::loadMap()` ==> `dlgProfilePreferences::slot_loadMap()` * `dlgProfilePreferences::resetColors()` ==> `dlgProfilePreferences::slot_resetColors()` * `dlgProfilePreferences::resetColors2()` ==> `dlgProfilePreferences::slot_resetMapColors()` * `dlgProfilePreferences::saveMap()` ==> `dlgProfilePreferences::slot_saveMap()` * `dlgProfilePreferences::setBgColor()` ==> `dlgProfilePreferences::slot_setBgColor()` * `dlgProfilePreferences::setBgColor2()` ==> `dlgProfilePreferences::slot_setMapBgColor()` * `dlgProfilePreferences::setColorBlack()` ==> `dlgProfilePreferences::slot_setColorBlack()` * `dlgProfilePreferences::setColorBlack2()` ==> `dlgProfilePreferences::slot_setMapColorBlack()` * `dlgProfilePreferences::setColorBlue()` ==> `dlgProfilePreferences::slot_setColorBlue()` * `dlgProfilePreferences::setColorCyan()` ==> `dlgProfilePreferences::slot_setColorCyan()` * `dlgProfilePreferences::setColorBlue2()` ==> `dlgProfilePreferences::slot_setMapColorBlue()` * `dlgProfilePreferences::setColorCyan2()` ==> `dlgProfilePreferences::slot_setMapColorCyan()` * `dlgProfilePreferences::setColorGreen()` ==> `dlgProfilePreferences::slot_setColorGreen()` * `dlgProfilePreferences::setColorGreen2()` ==> `dlgProfilePreferences::slot_setMapColorGreen()` * `dlgProfilePreferences::setColorLightBlack()` ==> `dlgProfilePreferences::slot_setColorLightBlack()` * `dlgProfilePreferences::setColorLightBlack2()` ==> `dlgProfilePreferences::slot_setMapColorLightBlack()` * `dlgProfilePreferences::setColorLightBlue()` ==> `dlgProfilePreferences::slot_setColorLightBlue()` * `dlgProfilePreferences::setColorLightBlue2()` ==> `dlgProfilePreferences::slot_setMapColorLightBlue()` * `dlgProfilePreferences::setColorLightCyan()` ==> `dlgProfilePreferences::slot_setColorLightCyan()` * `dlgProfilePreferences::setColorLightCyan2()` ==> `dlgProfilePreferences::slot_setMapColorLightCyan()` * `dlgProfilePreferences::setColorLightGreen()` ==> `dlgProfilePreferences::slot_setColorLightGreen()` * `dlgProfilePreferences::setColorLightGreen2()` ==> `dlgProfilePreferences::slot_setMapColorLightGreen()` * `dlgProfilePreferences::setColorLightMagenta()` ==> `dlgProfilePreferences::slot_setColorLightMagenta()` * `dlgProfilePreferences::setColorLightMagenta2()` ==> `dlgProfilePreferences::slot_setMapColorLightMagenta()` * `dlgProfilePreferences::setColorLightRed()` ==> `dlgProfilePreferences::slot_setColorLightRed()` * `dlgProfilePreferences::setColorLightRed2()` ==> `dlgProfilePreferences::slot_setMapColorLightRed()` * `dlgProfilePreferences::setColorLightWhite()` ==> `dlgProfilePreferences::slot_setColorLightWhite()` * `dlgProfilePreferences::setColorLightWhite2()` ==> `dlgProfilePreferences::slot_setMapColorLightWhite()` * `dlgProfilePreferences::setColorLightYellow()` ==> `dlgProfilePreferences::slot_setColorLightYellow()` * `dlgProfilePreferences::setColorLightYellow2()` ==> `dlgProfilePreferences::slot_setMapColorLightYellow()` * `dlgProfilePreferences::setColorMagenta()` ==> `dlgProfilePreferences::slot_setColorMagenta()` * `dlgProfilePreferences::setColorMagenta2()` ==> `dlgProfilePreferences::slot_setMapColorMagenta()` * `dlgProfilePreferences::setColorRed2()` ==> `dlgProfilePreferences::slot_setMapColorRed()` * `dlgProfilePreferences::setColorRed()` ==> `dlgProfilePreferences::slot_setColorRed()` * `dlgProfilePreferences::setColorWhite()` ==> `dlgProfilePreferences::slot_setColorWhite()` * `dlgProfilePreferences::setColorWhite2()` ==> `dlgProfilePreferences::slot_setMapColorWhite()` * `dlgProfilePreferences::setColorYellow()` ==> `dlgProfilePreferences::slot_setColorYellow()` * `dlgProfilePreferences::setColorYellow2()` ==> `dlgProfilePreferences::slot_setMapColorYellow()` * `dlgProfilePreferences::setCommandBgColor()` ==> `dlgProfilePreferences::slot_setCommandBgColor()` * `dlgProfilePreferences::setCommandFgColor()` ==> `dlgProfilePreferences::slot_setCommandFgColor()` * `dlgProfilePreferences::setCommandLineBgColor()` ==> `dlgProfilePreferences::slot_setCommandLineBgColor()` * `dlgProfilePreferences::setCommandLineFgColor()` ==> `dlgProfilePreferences::slot_setCommandLineFgColor()` * `dlgProfilePreferences::setDisplayFont()` ==> `dlgProfilePreferences::slot_setDisplayFont()` * `dlgProfilePreferences::setFgColor()` ==> `dlgProfilePreferences::slot_setFgColor()` * `dlgProfilePreferences::setFgColor2()` ==> `dlgProfilePreferences::slot_setMapExitsColor()` * `dlgProfilePreferences::setFontSize()` ==> `dlgProfilePreferences::slot_setFontSize()` * `dlgProfilePreferences::setMapInfoBackground()` ==> `dlgProfilePreferences::slot_setMapInfoBgColor()` * `dlgProfilePreferences::setRoomBorderColor()` ==> `dlgProfilePreferences::slot_setMapRoomBorderColor()` * `dlgProfilePreferences::slot_script_selected(...)` ==> `dlgProfilePreferences::slot_scriptSelected(...)` * `dlgProfilePreferences::slot_theme_selected(...)` ==> `dlgProfilePreferences::slot_themeSelected(...)` * `dlgRoomSymbol::colorRejected()` ==> `dlgRoomSymbol::slot_colorRejected()` * `dlgRoomSymbol::colorSelected(...)` ==> `dlgRoomSymbol::slot_colorSelected(...)` * `dlgRoomSymbol::currentColorChanged(...)` ==> `dlgRoomSymbol::slot_currentColorChanged(...)` * `dlgRoomSymbol::openColorSelector()` ==> `dlgRoomSymbol::slot_openColorSelector()` * `dlgRoomSymbol::resetColor()` ==> `dlgRoomSymbol::slot_resetColors()` * `dlgRoomSymbol::updatePreview()` ==> `dlgRoomSymbol::slot_updatePreview()` * `dlgTriggerEditor::slot_show_vars()` ==> `dlgTriggerEditor::slot_showVariables()` * `dlgTriggerEditor::slot_var_changed(...)` ==> `dlgTriggerEditor::slot_variableChanged(...)` * `dlgTriggerEditor::slot_var_selected(...)` ==> `dlgTriggerEditor::slot_variableSelected(...)` * `mudlet::slot_check_manual_update()` ==> `mudlet::slot_manualUpdateCheck()` * `mudlet::slot_close_current_profile()` ==> `mudlet::slot_closeCurrentProfile()` * `mudlet::slot_close_profile_requested(...)` ==> `mudlet::slot_closeProfileRequested(...)` * `mudlet::slot_connection_dlg_finished(...)` ==> `mudlet::slot_connectionDialogueFinished(...)` * `mudlet::slot_module_manager()` ==> `mudlet::slot_moduleManager()` * `mudlet::slot_mudlet_discord()` ==> `mudlet::slot_mudletDiscord()` * `mudlet::slot_multi_view(...)` ==> `mudlet::slot_multiView(const bool state)` * `mudlet::slot_package_manager()` ==> `mudlet::slot_packageManager()` * `mudlet::slot_package_exporter()` ==> `mudlet::slot_packageExporter()` * `mudlet::slot_timer_fires()` ==> `mudlet::slot_timerFires()` * `mudlet::slot_toggle_multi_view()` ==> `mudlet::slot_toggleMultiView()` Also the names for these in particular have been changed to make more sense: * `dlgProfilePreferences::slot_chooseProfilesChanged()` ==> `dlgProfilePreferences::slot_chosenProfilesChanged()` * `dlgProfilePreferences::slot_editor_tab_selected(...)` ==> `dlgProfilePreferences::slot_tabChanged(...)` * `dlgProfilePreferences::slot_passwords_location_changed(...)` ==> `dlgProfilePreferences::slot_passwordStorageLocationChanged(...)` * `dlgProfilePreferences::slot_save_and_exit()` ==> `dlgProfilePreferences::slot_saveAndClose()` * `dlgTriggerEditor::slot_toggleHiddenVar(...)` ==> `dlgTriggerEditor::slot_hideVariable(...)` - changed to distinguish it from `slot_toggleHiddenVariables(...)` * `dlgTriggerEditor::slot_item_selected_save(...)` ==> `dlgTriggerEditor::slot_saveSelectedItem(...)` * `mudlet::slot_discord()` ==> `mudlet::slot_profileDiscord()` - changed to distinguish it from `slot_mudletDiscord()` Not currently used and commented out: * `dlgProfilePreferences::setCommandLineFont()` ==> `dlgProfilePreferences::slot_setCommandLineFont()` Signed-off-by: Stephen Lyons <slysven@virginmedia.com>
2022-08-29 14:27:13 +02:00
void dlgConnectionProfiles::slot_updateDescription()
{
QListWidgetItem* pItem = listWidget_profiles->currentItem();
2010-03-15 09:37:16 +01:00
if (pItem) {
const QString description = mud_description_textedit->toPlainText();
writeProfileData(pItem->data(csmNameRole).toString(), qsl("description"), description);
Improve first-time user experience (#1016) * Fixed the welcome message to show again It didn't before because default_host profile directory was getting created on the disk before the check. * Made it so a random profile gets selected for a first-time launch * Modernized mudlet::startAutoLogin function * Renamed poorly-named method to open connections dialog It also was confusing with Host::connectToServer doing the actual connecting * Made the connection dialog open if no profiles are on autoload * Removed superseded status bar notification The connection dialog now opens automatically. * Improved first-launch Mudlet size and positioning Mudlet is now centered and has a decent size for the most common resolutions * Fix connection dialog to get a decent size by default * Updated the welcome text It was pretty ancient and referenced outdated button names. Also cut down on the wording so a new user isn't hit with a wall of text. * Moved icon over to left so the screen isn't so busy * Undid hardcoded fonts and sizes in connection dialog User-selected desktop fonts and sizes should be used instead. * Fixed connection dialog to actually select the last played profile There was a bug where it would ignore predefined profiles in the calculation. Also use the last modified, not read date - a lot of things like virus scanners could read the directory whereas far fewer things will write to it. * Moved 'loaded profile' warning to be below profiles This prevents the whole thing from jumping around. * Added a margin to the right for aesthetic reasons * Revised to use getDescription as a method * Got tooltips to show on profile icons
2017-06-04 08:13:51 +02:00
// don't display custom profile descriptions as a tooltip, as passwords could be stored in there
}
}
Add: open .mpackage files with Mudlet (#7065) #### Brief overview of PR changes/additions - Created `MudletServer` to keep track of Mudlet instances and allow them to communicate. - Added cli argument for installing a package. - Created `FileOpenHandler` for installing packages from `QEvent::FileOpen` events. - Updated mudlet.desktop to associate Mudlet with .zip files on Linux. - Created Info.plist to associate Mudlet with .mpackage files on MacOS. - Added registry keys to associate Mudlet with .mpackage files on Windows. #### Motivation for adding to Mudlet fixes #1083 /claim #1083 #### Other info (issues closed, discussion etc) The Info.plist file needs to be copied into the app bundle for MacOS to recognize it. I'm not sure how to do that. I found a reference to mudlet.app in `CI/travis.osx.after_success.sh`, but I think that's just for the CI build. Does someone know how Mudlet is installed on MacOS? Related docs: https://developer.apple.com/library/archive/documentation/CoreFoundation/Conceptual/CFBundles/BundleTypes/BundleTypes.html#//apple_ref/doc/uid/10000123i-CH101-SW1 ## Examples **Installing a package on an already running instance of Mudlet** https://github.com/Mudlet/Mudlet/assets/147658676/4457e496-a24f-420f-9bc3-3a879e7e4f9f **Queueing two packages for install by gui and cli. Displaying connection dialogue to determine which profile the packages should be installed on.** https://github.com/Mudlet/Mudlet/assets/147658676/a12edeea-6973-43c8-a691-cfb4524efdb1 **If no other instance of Mudlet is open, open Mudlet and install on first opened profile** https://github.com/Mudlet/Mudlet/assets/147658676/662d450a-6bbd-4528-88e6-4dba674cc82e
2024-01-07 08:48:57 -05:00
void dlgConnectionProfiles::indicatePackagesInstallOnConnect(QStringList packages)
{
if (packages.isEmpty()) {
Add: open .mpackage files with Mudlet (#7065) #### Brief overview of PR changes/additions - Created `MudletServer` to keep track of Mudlet instances and allow them to communicate. - Added cli argument for installing a package. - Created `FileOpenHandler` for installing packages from `QEvent::FileOpen` events. - Updated mudlet.desktop to associate Mudlet with .zip files on Linux. - Created Info.plist to associate Mudlet with .mpackage files on MacOS. - Added registry keys to associate Mudlet with .mpackage files on Windows. #### Motivation for adding to Mudlet fixes #1083 /claim #1083 #### Other info (issues closed, discussion etc) The Info.plist file needs to be copied into the app bundle for MacOS to recognize it. I'm not sure how to do that. I found a reference to mudlet.app in `CI/travis.osx.after_success.sh`, but I think that's just for the CI build. Does someone know how Mudlet is installed on MacOS? Related docs: https://developer.apple.com/library/archive/documentation/CoreFoundation/Conceptual/CFBundles/BundleTypes/BundleTypes.html#//apple_ref/doc/uid/10000123i-CH101-SW1 ## Examples **Installing a package on an already running instance of Mudlet** https://github.com/Mudlet/Mudlet/assets/147658676/4457e496-a24f-420f-9bc3-3a879e7e4f9f **Queueing two packages for install by gui and cli. Displaying connection dialogue to determine which profile the packages should be installed on.** https://github.com/Mudlet/Mudlet/assets/147658676/a12edeea-6973-43c8-a691-cfb4524efdb1 **If no other instance of Mudlet is open, open Mudlet and install on first opened profile** https://github.com/Mudlet/Mudlet/assets/147658676/662d450a-6bbd-4528-88e6-4dba674cc82e
2024-01-07 08:48:57 -05:00
return;
}
QWidget widget;
QGroupBox* packageGroupBox = new QGroupBox("Select and load a profile to install the following package(s) into:", this);
QVBoxLayout* packageInfoLayout = new QVBoxLayout(packageGroupBox);
packageInfoLayout->setContentsMargins(8, 8, 8, 8);
packageGroupBox->setStyleSheet("QGroupBox:title { padding-left: 8px; }");
for (const QString& package : packages) {
QFileInfo fileInfo(package);
QString packageName = fileInfo.baseName();
QLabel* packageLabel = new QLabel(packageName);
packageInfoLayout->addWidget(packageLabel);
}
layout()->addWidget(packageGroupBox);
}
Infrastructure: tidy up naming of SLOT methods and their usage - Part 2 (#6255) When Qt's slot/signal system is used to invoke a method there is some overhead - so it makes sense to ensure developers can spot all such methods (functions). We have tried to do this with a `slot_` prefix to the methods we create but it has not been applied uniformly. This PR (and one or more to follow) is intended to help with this by more rigorously doing so - the names changed herein should all follow a `slot_`*camelCaseMethodName* style. In addition some methods were detected that are not currently used or which are not actually used via the signal/slot system, these have been commented out or have had the prefix removed and the declaration in the relevant header file moved as appropriate. For reference the changes made are: * `TLuaInterpreter::slotDeleteSender(...)` ==> `TLuaInterpreter::slot_deleteSender(...)` * `TLuaInterpreter::slotPurge()` ==> `TLuaInterpreter::slot_purge()` * `cTelnet::handle_socket_signal_connected()` ==> `cTelnet::slot_socketConnected()` * `cTelnet::handle_socket_signal_disconnected()` ==> `cTelnet::slot_socketDisconnected()` * `cTelnet::handle_socket_signal_hostFound(...)` ==> `cTelnet::slot_socketHostFound(...)` * `cTelnet::handle_socket_signal_readyRead()` ==> `cTelnet::slot_socketReadyToBeRead()` * `cTelnet::handle_socket_signal_sslError(...)` ==> `cTelnet::slot_socketSslError(...)` * `cTelnet::setDownloadProgress(...)` ==> `cTelnet::slot_setDownloadProgress(...)` * `dlgComposer::cancel()` ==> `dlgComposer::slot_cancel()` * `dlgComposer::save()` ==> `dlgComposer::slot_save()` * `dlgConnectionProfiles::slot_item_clicked(...)` ==> `dlgConnectionProfiles::slot_itemClicked(...)` * `dlgConnectionProfiles::slot_save_name()` ==> `dlgConnectionProfiles::slot_saveName()` * `dlgConnectionProfiles::slot_update_login(...)` ==> `dlgConnectionProfiles::slot_updateLogin(...)` * `dlgConnectionProfiles::slot_update_name(...)` ==> `dlgConnectionProfiles::slot_updateName(...)` * `dlgConnectionProfiles::slot_update_pass(...)` ==> `dlgConnectionProfiles::slot_updatePassword(...)` * `dlgConnectionProfiles::slot_update_port(...)` ==> `dlgConnectionProfiles::slot_updatePort(...)` * `dlgConnectionProfiles::slot_update_SSL_TSL_port(...)` ==> `dlgConnectionProfiles::slot_updateSslTslPort(...)` * `dlgConnectionProfiles::slot_update_url(...)` ==> `dlgConnectionProfiles::slot_updateUrl(...)` * `dlgMapLabel::pickBgColor()` ==> `dlgMapLabel::slot_pickBgColor()` * `dlgMapLabel::pickFgColor()` ==> `dlgMapLabel::slot_pickFgColor()` * `dlgMapLabel::pickFile()` ==> `dlgMapLabel::slot_pickFile()` * `dlgMapLabel::pickFont()` ==> `dlgMapLabel::slot_pickFont()` * `dlgMapLabel::save()` ==> `dlgMapLabel::slot_save()` * `dlgMapLabel::updateControls()` ==> `dlgMapLabel::slot_updateControls()` * `dlgMapLabel::updateControlsVisibility()` ==> `dlgMapLabel::slot_updateControlsVisibility()` Commented out as not being used: * `cTelnet::handle_socket_signal_error()` ==> `cTelnet::slot_socketError()` * `dlgConnectionProfiles::slot_update_website(...) ==> `dlgConnectionProfiles::slot_updateWebsite(const QString& url)` I have my doubts about whether the `dlgMapLabel::save()`/`dlgMapLabel::slot_save()` method is needed at all, as it might be that the `QDialog::accept()` slot could have been inserted into the `QObject::connect(...)` call directly instead...? Signed-off-by: Stephen Lyons <slysven@virginmedia.com>
2022-08-27 10:47:01 +02:00
// Not used:
//void dlgConnectionProfiles::slot_updateWebsite(const QString& url)
//{
// QListWidgetItem* pItem = listWidget_profiles->currentItem();
Infrastructure: tidy up naming of SLOT methods and their usage - Part 2 (#6255) When Qt's slot/signal system is used to invoke a method there is some overhead - so it makes sense to ensure developers can spot all such methods (functions). We have tried to do this with a `slot_` prefix to the methods we create but it has not been applied uniformly. This PR (and one or more to follow) is intended to help with this by more rigorously doing so - the names changed herein should all follow a `slot_`*camelCaseMethodName* style. In addition some methods were detected that are not currently used or which are not actually used via the signal/slot system, these have been commented out or have had the prefix removed and the declaration in the relevant header file moved as appropriate. For reference the changes made are: * `TLuaInterpreter::slotDeleteSender(...)` ==> `TLuaInterpreter::slot_deleteSender(...)` * `TLuaInterpreter::slotPurge()` ==> `TLuaInterpreter::slot_purge()` * `cTelnet::handle_socket_signal_connected()` ==> `cTelnet::slot_socketConnected()` * `cTelnet::handle_socket_signal_disconnected()` ==> `cTelnet::slot_socketDisconnected()` * `cTelnet::handle_socket_signal_hostFound(...)` ==> `cTelnet::slot_socketHostFound(...)` * `cTelnet::handle_socket_signal_readyRead()` ==> `cTelnet::slot_socketReadyToBeRead()` * `cTelnet::handle_socket_signal_sslError(...)` ==> `cTelnet::slot_socketSslError(...)` * `cTelnet::setDownloadProgress(...)` ==> `cTelnet::slot_setDownloadProgress(...)` * `dlgComposer::cancel()` ==> `dlgComposer::slot_cancel()` * `dlgComposer::save()` ==> `dlgComposer::slot_save()` * `dlgConnectionProfiles::slot_item_clicked(...)` ==> `dlgConnectionProfiles::slot_itemClicked(...)` * `dlgConnectionProfiles::slot_save_name()` ==> `dlgConnectionProfiles::slot_saveName()` * `dlgConnectionProfiles::slot_update_login(...)` ==> `dlgConnectionProfiles::slot_updateLogin(...)` * `dlgConnectionProfiles::slot_update_name(...)` ==> `dlgConnectionProfiles::slot_updateName(...)` * `dlgConnectionProfiles::slot_update_pass(...)` ==> `dlgConnectionProfiles::slot_updatePassword(...)` * `dlgConnectionProfiles::slot_update_port(...)` ==> `dlgConnectionProfiles::slot_updatePort(...)` * `dlgConnectionProfiles::slot_update_SSL_TSL_port(...)` ==> `dlgConnectionProfiles::slot_updateSslTslPort(...)` * `dlgConnectionProfiles::slot_update_url(...)` ==> `dlgConnectionProfiles::slot_updateUrl(...)` * `dlgMapLabel::pickBgColor()` ==> `dlgMapLabel::slot_pickBgColor()` * `dlgMapLabel::pickFgColor()` ==> `dlgMapLabel::slot_pickFgColor()` * `dlgMapLabel::pickFile()` ==> `dlgMapLabel::slot_pickFile()` * `dlgMapLabel::pickFont()` ==> `dlgMapLabel::slot_pickFont()` * `dlgMapLabel::save()` ==> `dlgMapLabel::slot_save()` * `dlgMapLabel::updateControls()` ==> `dlgMapLabel::slot_updateControls()` * `dlgMapLabel::updateControlsVisibility()` ==> `dlgMapLabel::slot_updateControlsVisibility()` Commented out as not being used: * `cTelnet::handle_socket_signal_error()` ==> `cTelnet::slot_socketError()` * `dlgConnectionProfiles::slot_update_website(...) ==> `dlgConnectionProfiles::slot_updateWebsite(const QString& url)` I have my doubts about whether the `dlgMapLabel::save()`/`dlgMapLabel::slot_save()` method is needed at all, as it might be that the `QDialog::accept()` slot could have been inserted into the `QObject::connect(...)` call directly instead...? Signed-off-by: Stephen Lyons <slysven@virginmedia.com>
2022-08-27 10:47:01 +02:00
// if (pItem) {
// writeProfileData(pItem->data(csmNameRole).toString(), qsl("website"), url);
// }
//}
Infrastructure: tidy up naming of SLOT methods and their usage - Part 2 (#6255) When Qt's slot/signal system is used to invoke a method there is some overhead - so it makes sense to ensure developers can spot all such methods (functions). We have tried to do this with a `slot_` prefix to the methods we create but it has not been applied uniformly. This PR (and one or more to follow) is intended to help with this by more rigorously doing so - the names changed herein should all follow a `slot_`*camelCaseMethodName* style. In addition some methods were detected that are not currently used or which are not actually used via the signal/slot system, these have been commented out or have had the prefix removed and the declaration in the relevant header file moved as appropriate. For reference the changes made are: * `TLuaInterpreter::slotDeleteSender(...)` ==> `TLuaInterpreter::slot_deleteSender(...)` * `TLuaInterpreter::slotPurge()` ==> `TLuaInterpreter::slot_purge()` * `cTelnet::handle_socket_signal_connected()` ==> `cTelnet::slot_socketConnected()` * `cTelnet::handle_socket_signal_disconnected()` ==> `cTelnet::slot_socketDisconnected()` * `cTelnet::handle_socket_signal_hostFound(...)` ==> `cTelnet::slot_socketHostFound(...)` * `cTelnet::handle_socket_signal_readyRead()` ==> `cTelnet::slot_socketReadyToBeRead()` * `cTelnet::handle_socket_signal_sslError(...)` ==> `cTelnet::slot_socketSslError(...)` * `cTelnet::setDownloadProgress(...)` ==> `cTelnet::slot_setDownloadProgress(...)` * `dlgComposer::cancel()` ==> `dlgComposer::slot_cancel()` * `dlgComposer::save()` ==> `dlgComposer::slot_save()` * `dlgConnectionProfiles::slot_item_clicked(...)` ==> `dlgConnectionProfiles::slot_itemClicked(...)` * `dlgConnectionProfiles::slot_save_name()` ==> `dlgConnectionProfiles::slot_saveName()` * `dlgConnectionProfiles::slot_update_login(...)` ==> `dlgConnectionProfiles::slot_updateLogin(...)` * `dlgConnectionProfiles::slot_update_name(...)` ==> `dlgConnectionProfiles::slot_updateName(...)` * `dlgConnectionProfiles::slot_update_pass(...)` ==> `dlgConnectionProfiles::slot_updatePassword(...)` * `dlgConnectionProfiles::slot_update_port(...)` ==> `dlgConnectionProfiles::slot_updatePort(...)` * `dlgConnectionProfiles::slot_update_SSL_TSL_port(...)` ==> `dlgConnectionProfiles::slot_updateSslTslPort(...)` * `dlgConnectionProfiles::slot_update_url(...)` ==> `dlgConnectionProfiles::slot_updateUrl(...)` * `dlgMapLabel::pickBgColor()` ==> `dlgMapLabel::slot_pickBgColor()` * `dlgMapLabel::pickFgColor()` ==> `dlgMapLabel::slot_pickFgColor()` * `dlgMapLabel::pickFile()` ==> `dlgMapLabel::slot_pickFile()` * `dlgMapLabel::pickFont()` ==> `dlgMapLabel::slot_pickFont()` * `dlgMapLabel::save()` ==> `dlgMapLabel::slot_save()` * `dlgMapLabel::updateControls()` ==> `dlgMapLabel::slot_updateControls()` * `dlgMapLabel::updateControlsVisibility()` ==> `dlgMapLabel::slot_updateControlsVisibility()` Commented out as not being used: * `cTelnet::handle_socket_signal_error()` ==> `cTelnet::slot_socketError()` * `dlgConnectionProfiles::slot_update_website(...) ==> `dlgConnectionProfiles::slot_updateWebsite(const QString& url)` I have my doubts about whether the `dlgMapLabel::save()`/`dlgMapLabel::slot_save()` method is needed at all, as it might be that the `QDialog::accept()` slot could have been inserted into the `QObject::connect(...)` call directly instead...? Signed-off-by: Stephen Lyons <slysven@virginmedia.com>
2022-08-27 10:47:01 +02:00
void dlgConnectionProfiles::slot_updatePassword(const QString& pass)
{
QListWidgetItem* pItem = listWidget_profiles->currentItem();
if (!pItem) {
return;
}
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 06:44:38 -04:00
const QString profileName = pItem->data(csmNameRole).toString();
2025-09-13 14:17:13 +07:00
if (mudlet::self()->storingPasswordsSecurely()) {
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 06:44:38 -04:00
if (pass.trimmed().isEmpty()) {
// If password is empty, remove it from secure storage
deleteSecurePassword(profileName);
} else {
// Store the password securely
writeSecurePassword(profileName, pass);
}
} else {
auto result = mudlet::self()->writeProfileData(profileName, qsl("password"), pass);
if (!result.first) {
qWarning().noquote().nospace() << "dlgConnectionProfiles::slot_updatePassword() ERROR - failed to save password for profile \"" << profileName << "\": " << result.second;
}
}
}
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 16:42:09 +12:00
void dlgConnectionProfiles::writeSecurePassword(const QString& profile, const QString& pass)
{
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 06:44:38 -04:00
// Validate that we have a password to store
if (pass.trimmed().isEmpty()) {
qDebug() << "dlgConnectionProfiles: Skipping storage of empty password for profile" << profile;
return;
}
2025-09-13 14:17:13 +07:00
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 06:44:38 -04:00
// Use async API for QtKeychain integration with file fallback
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 16:42:09 +12:00
auto* credManager = new CredentialManager(this);
2025-09-13 14:17:13 +07:00
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 06:44:38 -04:00
credManager->storePassword(profile, "character", pass, [credManager, profile](bool success, const QString& errorMessage) {
if (success) {
qDebug() << "dlgConnectionProfiles: Successfully stored password for profile" << profile;
} else {
qWarning() << "dlgConnectionProfiles: Failed to store password for profile" << profile << ":" << errorMessage;
}
2025-09-13 14:17:13 +07:00
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 06:44:38 -04:00
// Clean up the credential manager
credManager->deleteLater();
});
}
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 16:42:09 +12:00
void dlgConnectionProfiles::deleteSecurePassword(const QString& profile)
{
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 06:44:38 -04:00
// Use async API for QtKeychain integration with file fallback
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 16:42:09 +12:00
auto* credManager = new CredentialManager(this);
2025-09-13 14:17:13 +07:00
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 06:44:38 -04:00
credManager->removePassword(profile, "character", [credManager, profile](bool success, const QString& errorMessage) {
if (success) {
qDebug() << "dlgConnectionProfiles: Successfully removed password for profile" << profile;
} else {
qWarning() << "dlgConnectionProfiles: Failed to remove password for profile" << profile << ":" << errorMessage;
}
2025-09-13 14:17:13 +07:00
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 06:44:38 -04:00
// Clean up the credential manager
credManager->deleteLater();
});
}
Infrastructure: tidy up naming of SLOT methods and their usage - Part 2 (#6255) When Qt's slot/signal system is used to invoke a method there is some overhead - so it makes sense to ensure developers can spot all such methods (functions). We have tried to do this with a `slot_` prefix to the methods we create but it has not been applied uniformly. This PR (and one or more to follow) is intended to help with this by more rigorously doing so - the names changed herein should all follow a `slot_`*camelCaseMethodName* style. In addition some methods were detected that are not currently used or which are not actually used via the signal/slot system, these have been commented out or have had the prefix removed and the declaration in the relevant header file moved as appropriate. For reference the changes made are: * `TLuaInterpreter::slotDeleteSender(...)` ==> `TLuaInterpreter::slot_deleteSender(...)` * `TLuaInterpreter::slotPurge()` ==> `TLuaInterpreter::slot_purge()` * `cTelnet::handle_socket_signal_connected()` ==> `cTelnet::slot_socketConnected()` * `cTelnet::handle_socket_signal_disconnected()` ==> `cTelnet::slot_socketDisconnected()` * `cTelnet::handle_socket_signal_hostFound(...)` ==> `cTelnet::slot_socketHostFound(...)` * `cTelnet::handle_socket_signal_readyRead()` ==> `cTelnet::slot_socketReadyToBeRead()` * `cTelnet::handle_socket_signal_sslError(...)` ==> `cTelnet::slot_socketSslError(...)` * `cTelnet::setDownloadProgress(...)` ==> `cTelnet::slot_setDownloadProgress(...)` * `dlgComposer::cancel()` ==> `dlgComposer::slot_cancel()` * `dlgComposer::save()` ==> `dlgComposer::slot_save()` * `dlgConnectionProfiles::slot_item_clicked(...)` ==> `dlgConnectionProfiles::slot_itemClicked(...)` * `dlgConnectionProfiles::slot_save_name()` ==> `dlgConnectionProfiles::slot_saveName()` * `dlgConnectionProfiles::slot_update_login(...)` ==> `dlgConnectionProfiles::slot_updateLogin(...)` * `dlgConnectionProfiles::slot_update_name(...)` ==> `dlgConnectionProfiles::slot_updateName(...)` * `dlgConnectionProfiles::slot_update_pass(...)` ==> `dlgConnectionProfiles::slot_updatePassword(...)` * `dlgConnectionProfiles::slot_update_port(...)` ==> `dlgConnectionProfiles::slot_updatePort(...)` * `dlgConnectionProfiles::slot_update_SSL_TSL_port(...)` ==> `dlgConnectionProfiles::slot_updateSslTslPort(...)` * `dlgConnectionProfiles::slot_update_url(...)` ==> `dlgConnectionProfiles::slot_updateUrl(...)` * `dlgMapLabel::pickBgColor()` ==> `dlgMapLabel::slot_pickBgColor()` * `dlgMapLabel::pickFgColor()` ==> `dlgMapLabel::slot_pickFgColor()` * `dlgMapLabel::pickFile()` ==> `dlgMapLabel::slot_pickFile()` * `dlgMapLabel::pickFont()` ==> `dlgMapLabel::slot_pickFont()` * `dlgMapLabel::save()` ==> `dlgMapLabel::slot_save()` * `dlgMapLabel::updateControls()` ==> `dlgMapLabel::slot_updateControls()` * `dlgMapLabel::updateControlsVisibility()` ==> `dlgMapLabel::slot_updateControlsVisibility()` Commented out as not being used: * `cTelnet::handle_socket_signal_error()` ==> `cTelnet::slot_socketError()` * `dlgConnectionProfiles::slot_update_website(...) ==> `dlgConnectionProfiles::slot_updateWebsite(const QString& url)` I have my doubts about whether the `dlgMapLabel::save()`/`dlgMapLabel::slot_save()` method is needed at all, as it might be that the `QDialog::accept()` slot could have been inserted into the `QObject::connect(...)` call directly instead...? Signed-off-by: Stephen Lyons <slysven@virginmedia.com>
2022-08-27 10:47:01 +02:00
void dlgConnectionProfiles::slot_updateLogin(const QString& login)
{
QListWidgetItem* pItem = listWidget_profiles->currentItem();
if (pItem) {
const QString profileName = pItem->data(csmNameRole).toString();
auto result = mudlet::self()->writeProfileData(profileName, qsl("login"), login);
if (!result.first) {
qWarning().noquote().nospace() << "dlgConnectionProfiles::slot_updateLogin() ERROR - failed to save character name for profile \"" << profileName << "\": " << result.second;
// Could optionally show user notification here
}
}
}
Infrastructure: tidy up naming of SLOT methods and their usage - Part 2 (#6255) When Qt's slot/signal system is used to invoke a method there is some overhead - so it makes sense to ensure developers can spot all such methods (functions). We have tried to do this with a `slot_` prefix to the methods we create but it has not been applied uniformly. This PR (and one or more to follow) is intended to help with this by more rigorously doing so - the names changed herein should all follow a `slot_`*camelCaseMethodName* style. In addition some methods were detected that are not currently used or which are not actually used via the signal/slot system, these have been commented out or have had the prefix removed and the declaration in the relevant header file moved as appropriate. For reference the changes made are: * `TLuaInterpreter::slotDeleteSender(...)` ==> `TLuaInterpreter::slot_deleteSender(...)` * `TLuaInterpreter::slotPurge()` ==> `TLuaInterpreter::slot_purge()` * `cTelnet::handle_socket_signal_connected()` ==> `cTelnet::slot_socketConnected()` * `cTelnet::handle_socket_signal_disconnected()` ==> `cTelnet::slot_socketDisconnected()` * `cTelnet::handle_socket_signal_hostFound(...)` ==> `cTelnet::slot_socketHostFound(...)` * `cTelnet::handle_socket_signal_readyRead()` ==> `cTelnet::slot_socketReadyToBeRead()` * `cTelnet::handle_socket_signal_sslError(...)` ==> `cTelnet::slot_socketSslError(...)` * `cTelnet::setDownloadProgress(...)` ==> `cTelnet::slot_setDownloadProgress(...)` * `dlgComposer::cancel()` ==> `dlgComposer::slot_cancel()` * `dlgComposer::save()` ==> `dlgComposer::slot_save()` * `dlgConnectionProfiles::slot_item_clicked(...)` ==> `dlgConnectionProfiles::slot_itemClicked(...)` * `dlgConnectionProfiles::slot_save_name()` ==> `dlgConnectionProfiles::slot_saveName()` * `dlgConnectionProfiles::slot_update_login(...)` ==> `dlgConnectionProfiles::slot_updateLogin(...)` * `dlgConnectionProfiles::slot_update_name(...)` ==> `dlgConnectionProfiles::slot_updateName(...)` * `dlgConnectionProfiles::slot_update_pass(...)` ==> `dlgConnectionProfiles::slot_updatePassword(...)` * `dlgConnectionProfiles::slot_update_port(...)` ==> `dlgConnectionProfiles::slot_updatePort(...)` * `dlgConnectionProfiles::slot_update_SSL_TSL_port(...)` ==> `dlgConnectionProfiles::slot_updateSslTslPort(...)` * `dlgConnectionProfiles::slot_update_url(...)` ==> `dlgConnectionProfiles::slot_updateUrl(...)` * `dlgMapLabel::pickBgColor()` ==> `dlgMapLabel::slot_pickBgColor()` * `dlgMapLabel::pickFgColor()` ==> `dlgMapLabel::slot_pickFgColor()` * `dlgMapLabel::pickFile()` ==> `dlgMapLabel::slot_pickFile()` * `dlgMapLabel::pickFont()` ==> `dlgMapLabel::slot_pickFont()` * `dlgMapLabel::save()` ==> `dlgMapLabel::slot_save()` * `dlgMapLabel::updateControls()` ==> `dlgMapLabel::slot_updateControls()` * `dlgMapLabel::updateControlsVisibility()` ==> `dlgMapLabel::slot_updateControlsVisibility()` Commented out as not being used: * `cTelnet::handle_socket_signal_error()` ==> `cTelnet::slot_socketError()` * `dlgConnectionProfiles::slot_update_website(...) ==> `dlgConnectionProfiles::slot_updateWebsite(const QString& url)` I have my doubts about whether the `dlgMapLabel::save()`/`dlgMapLabel::slot_save()` method is needed at all, as it might be that the `QDialog::accept()` slot could have been inserted into the `QObject::connect(...)` call directly instead...? Signed-off-by: Stephen Lyons <slysven@virginmedia.com>
2022-08-27 10:47:01 +02:00
void dlgConnectionProfiles::slot_updateUrl(const QString& url)
{
if (url.isEmpty()) {
validUrl = false;
offline_button->setEnabled(false);
connect_button->setEnabled(false);
offline_button->setAccessibleDescription(btn_connOrLoad_disabled_accessDesc);
connect_button->setAccessibleDescription(btn_connOrLoad_disabled_accessDesc);
return;
}
2019-01-06 06:29:16 -05:00
if (validateProfile()) {
QListWidgetItem* pItem = listWidget_profiles->currentItem();
2019-01-06 06:29:16 -05:00
if (!pItem) {
return;
}
writeProfileData(pItem->data(csmNameRole).toString(), qsl("url"), host_name_entry->text());
}
}
Infrastructure: tidy up naming of SLOT methods and their usage - Part 5 (last) (#6266) When Qt's slot/signal system is used to invoke a method there is some overhead - so it makes sense to ensure developers can spot all such methods (functions). We have tried to do this with a `slot_` prefix to the methods we create but it has not been applied uniformly. This PR is intended to conclude helping with this by more rigorously doing so - the names changed herein should all follow a `slot_`*camelCaseMethodName* style. In addition some methods were detected that are not currently used or which are not actually used via the signal/slot system, these have been commented out or have had the prefix removed and the declaration in the relevant header file moved as appropriate. There were two similar sets of (not prefixed with `slot_`) slots in the Profile preferences class that adjusted the colour settings separately for the main console and the mapper the latter had the same names but with a `2` suffix. To made it more clear I have changed them to include `Map` in their names instead. Also, the NON-slot method: `(void) dlgProfilePreferences::setColor(QPushButton*, QColor&, bool)` has been renamed to: `(void) dlgProfilePreferences::setButtonAndProfileColor(QPushButton*, QColor&, bool)` so that it is clearly distinguishable from built in Qt methods that are also called `setColor` - though which do have different signatures! For reference the changes made are: * `TConsole::slot_stop_all_triggers(...)` ==> `TConsole::slot_stopAllItems(...)` * `dlgConnectionProfiles::slot_copy_profile()` ==> `dlgConnectionProfiles::slot_copyProfile()` * `dlgConnectionProfiles::slot_copy_profilesettings_only()` ==> `dlgConnectionProfiles::slot_copyOnlySettingsOfProfile()` * `dlgConnectionProfiles::slot_deleteprofile_check(...)` ==> `dlgConnectionProfiles::slot_deleteProfileCheck(...)` * `dlgConnectionProfiles::slot_password_deleted(...)` ==> `dlgConnectionProfiles::slot_passwordDeleted(...)` * `dlgConnectionProfiles::slot_password_saved(...) ==> `dlgConnectionProfiles::slot_passwordSaved(...)` * `dlgConnectionProfiles::slot_profile_menu(...)` ==> `dlgConnectionProfiles::slot_profileContextMenu(...)` * `dlgConnectionProfiles::slot_reset_custom_icon()` ==> `dlgConnectionProfiles::slot_resetCustomIcon()` * `dlgConnectionProfiles::slot_set_custom_icon()` ==> `dlgConnectionProfiles::slot_setCustomIcon()` * `dlgConnectionProfiles::slot_set_custom_color()` ==> `dlgConnectionProfiles::slot_setCustomColor()` * `dlgConnectionProfiles::slot_update_autologin(...)` ==> `dlgConnectionProfiles::slot_updateAutoConnect(...)` * `dlgConnectionProfiles::slot_update_autoreconnect(...)` ==> `dlgConnectionProfiles::slot_updateAutoReconnect(...)` * `dlgConnectionProfiles::slot_update_description()` ==> `dlgConnectionProfiles::slot_updateDescription()` * `dlgConnectionProfiles::slot_update_discord_optin(...)` ==> `dlgConnectionProfiles::slot_updateDiscordOptIn(...)` * `dlgProfilePreferences::copyMap()` ==> `dlgProfilePreferences::slot_copyMap()` * `dlgProfilePreferences::downloadMap()` ==> `dlgProfilePreferences::slot_downloadMap()` * `dlgProfilePreferences::hideActionLabel()` ==> `dlgProfilePreferences::slot_hideActionLabel()` * `dlgProfilePreferences::loadMap()` ==> `dlgProfilePreferences::slot_loadMap()` * `dlgProfilePreferences::resetColors()` ==> `dlgProfilePreferences::slot_resetColors()` * `dlgProfilePreferences::resetColors2()` ==> `dlgProfilePreferences::slot_resetMapColors()` * `dlgProfilePreferences::saveMap()` ==> `dlgProfilePreferences::slot_saveMap()` * `dlgProfilePreferences::setBgColor()` ==> `dlgProfilePreferences::slot_setBgColor()` * `dlgProfilePreferences::setBgColor2()` ==> `dlgProfilePreferences::slot_setMapBgColor()` * `dlgProfilePreferences::setColorBlack()` ==> `dlgProfilePreferences::slot_setColorBlack()` * `dlgProfilePreferences::setColorBlack2()` ==> `dlgProfilePreferences::slot_setMapColorBlack()` * `dlgProfilePreferences::setColorBlue()` ==> `dlgProfilePreferences::slot_setColorBlue()` * `dlgProfilePreferences::setColorCyan()` ==> `dlgProfilePreferences::slot_setColorCyan()` * `dlgProfilePreferences::setColorBlue2()` ==> `dlgProfilePreferences::slot_setMapColorBlue()` * `dlgProfilePreferences::setColorCyan2()` ==> `dlgProfilePreferences::slot_setMapColorCyan()` * `dlgProfilePreferences::setColorGreen()` ==> `dlgProfilePreferences::slot_setColorGreen()` * `dlgProfilePreferences::setColorGreen2()` ==> `dlgProfilePreferences::slot_setMapColorGreen()` * `dlgProfilePreferences::setColorLightBlack()` ==> `dlgProfilePreferences::slot_setColorLightBlack()` * `dlgProfilePreferences::setColorLightBlack2()` ==> `dlgProfilePreferences::slot_setMapColorLightBlack()` * `dlgProfilePreferences::setColorLightBlue()` ==> `dlgProfilePreferences::slot_setColorLightBlue()` * `dlgProfilePreferences::setColorLightBlue2()` ==> `dlgProfilePreferences::slot_setMapColorLightBlue()` * `dlgProfilePreferences::setColorLightCyan()` ==> `dlgProfilePreferences::slot_setColorLightCyan()` * `dlgProfilePreferences::setColorLightCyan2()` ==> `dlgProfilePreferences::slot_setMapColorLightCyan()` * `dlgProfilePreferences::setColorLightGreen()` ==> `dlgProfilePreferences::slot_setColorLightGreen()` * `dlgProfilePreferences::setColorLightGreen2()` ==> `dlgProfilePreferences::slot_setMapColorLightGreen()` * `dlgProfilePreferences::setColorLightMagenta()` ==> `dlgProfilePreferences::slot_setColorLightMagenta()` * `dlgProfilePreferences::setColorLightMagenta2()` ==> `dlgProfilePreferences::slot_setMapColorLightMagenta()` * `dlgProfilePreferences::setColorLightRed()` ==> `dlgProfilePreferences::slot_setColorLightRed()` * `dlgProfilePreferences::setColorLightRed2()` ==> `dlgProfilePreferences::slot_setMapColorLightRed()` * `dlgProfilePreferences::setColorLightWhite()` ==> `dlgProfilePreferences::slot_setColorLightWhite()` * `dlgProfilePreferences::setColorLightWhite2()` ==> `dlgProfilePreferences::slot_setMapColorLightWhite()` * `dlgProfilePreferences::setColorLightYellow()` ==> `dlgProfilePreferences::slot_setColorLightYellow()` * `dlgProfilePreferences::setColorLightYellow2()` ==> `dlgProfilePreferences::slot_setMapColorLightYellow()` * `dlgProfilePreferences::setColorMagenta()` ==> `dlgProfilePreferences::slot_setColorMagenta()` * `dlgProfilePreferences::setColorMagenta2()` ==> `dlgProfilePreferences::slot_setMapColorMagenta()` * `dlgProfilePreferences::setColorRed2()` ==> `dlgProfilePreferences::slot_setMapColorRed()` * `dlgProfilePreferences::setColorRed()` ==> `dlgProfilePreferences::slot_setColorRed()` * `dlgProfilePreferences::setColorWhite()` ==> `dlgProfilePreferences::slot_setColorWhite()` * `dlgProfilePreferences::setColorWhite2()` ==> `dlgProfilePreferences::slot_setMapColorWhite()` * `dlgProfilePreferences::setColorYellow()` ==> `dlgProfilePreferences::slot_setColorYellow()` * `dlgProfilePreferences::setColorYellow2()` ==> `dlgProfilePreferences::slot_setMapColorYellow()` * `dlgProfilePreferences::setCommandBgColor()` ==> `dlgProfilePreferences::slot_setCommandBgColor()` * `dlgProfilePreferences::setCommandFgColor()` ==> `dlgProfilePreferences::slot_setCommandFgColor()` * `dlgProfilePreferences::setCommandLineBgColor()` ==> `dlgProfilePreferences::slot_setCommandLineBgColor()` * `dlgProfilePreferences::setCommandLineFgColor()` ==> `dlgProfilePreferences::slot_setCommandLineFgColor()` * `dlgProfilePreferences::setDisplayFont()` ==> `dlgProfilePreferences::slot_setDisplayFont()` * `dlgProfilePreferences::setFgColor()` ==> `dlgProfilePreferences::slot_setFgColor()` * `dlgProfilePreferences::setFgColor2()` ==> `dlgProfilePreferences::slot_setMapExitsColor()` * `dlgProfilePreferences::setFontSize()` ==> `dlgProfilePreferences::slot_setFontSize()` * `dlgProfilePreferences::setMapInfoBackground()` ==> `dlgProfilePreferences::slot_setMapInfoBgColor()` * `dlgProfilePreferences::setRoomBorderColor()` ==> `dlgProfilePreferences::slot_setMapRoomBorderColor()` * `dlgProfilePreferences::slot_script_selected(...)` ==> `dlgProfilePreferences::slot_scriptSelected(...)` * `dlgProfilePreferences::slot_theme_selected(...)` ==> `dlgProfilePreferences::slot_themeSelected(...)` * `dlgRoomSymbol::colorRejected()` ==> `dlgRoomSymbol::slot_colorRejected()` * `dlgRoomSymbol::colorSelected(...)` ==> `dlgRoomSymbol::slot_colorSelected(...)` * `dlgRoomSymbol::currentColorChanged(...)` ==> `dlgRoomSymbol::slot_currentColorChanged(...)` * `dlgRoomSymbol::openColorSelector()` ==> `dlgRoomSymbol::slot_openColorSelector()` * `dlgRoomSymbol::resetColor()` ==> `dlgRoomSymbol::slot_resetColors()` * `dlgRoomSymbol::updatePreview()` ==> `dlgRoomSymbol::slot_updatePreview()` * `dlgTriggerEditor::slot_show_vars()` ==> `dlgTriggerEditor::slot_showVariables()` * `dlgTriggerEditor::slot_var_changed(...)` ==> `dlgTriggerEditor::slot_variableChanged(...)` * `dlgTriggerEditor::slot_var_selected(...)` ==> `dlgTriggerEditor::slot_variableSelected(...)` * `mudlet::slot_check_manual_update()` ==> `mudlet::slot_manualUpdateCheck()` * `mudlet::slot_close_current_profile()` ==> `mudlet::slot_closeCurrentProfile()` * `mudlet::slot_close_profile_requested(...)` ==> `mudlet::slot_closeProfileRequested(...)` * `mudlet::slot_connection_dlg_finished(...)` ==> `mudlet::slot_connectionDialogueFinished(...)` * `mudlet::slot_module_manager()` ==> `mudlet::slot_moduleManager()` * `mudlet::slot_mudlet_discord()` ==> `mudlet::slot_mudletDiscord()` * `mudlet::slot_multi_view(...)` ==> `mudlet::slot_multiView(const bool state)` * `mudlet::slot_package_manager()` ==> `mudlet::slot_packageManager()` * `mudlet::slot_package_exporter()` ==> `mudlet::slot_packageExporter()` * `mudlet::slot_timer_fires()` ==> `mudlet::slot_timerFires()` * `mudlet::slot_toggle_multi_view()` ==> `mudlet::slot_toggleMultiView()` Also the names for these in particular have been changed to make more sense: * `dlgProfilePreferences::slot_chooseProfilesChanged()` ==> `dlgProfilePreferences::slot_chosenProfilesChanged()` * `dlgProfilePreferences::slot_editor_tab_selected(...)` ==> `dlgProfilePreferences::slot_tabChanged(...)` * `dlgProfilePreferences::slot_passwords_location_changed(...)` ==> `dlgProfilePreferences::slot_passwordStorageLocationChanged(...)` * `dlgProfilePreferences::slot_save_and_exit()` ==> `dlgProfilePreferences::slot_saveAndClose()` * `dlgTriggerEditor::slot_toggleHiddenVar(...)` ==> `dlgTriggerEditor::slot_hideVariable(...)` - changed to distinguish it from `slot_toggleHiddenVariables(...)` * `dlgTriggerEditor::slot_item_selected_save(...)` ==> `dlgTriggerEditor::slot_saveSelectedItem(...)` * `mudlet::slot_discord()` ==> `mudlet::slot_profileDiscord()` - changed to distinguish it from `slot_mudletDiscord()` Not currently used and commented out: * `dlgProfilePreferences::setCommandLineFont()` ==> `dlgProfilePreferences::slot_setCommandLineFont()` Signed-off-by: Stephen Lyons <slysven@virginmedia.com>
2022-08-29 14:27:13 +02:00
void dlgConnectionProfiles::slot_updateAutoConnect(int state)
{
QListWidgetItem* pItem = listWidget_profiles->currentItem();
if (!pItem) {
return;
}
writeProfileData(pItem->data(csmNameRole).toString(), qsl("autologin"), QString::number(state));
}
Infrastructure: tidy up naming of SLOT methods and their usage - Part 5 (last) (#6266) When Qt's slot/signal system is used to invoke a method there is some overhead - so it makes sense to ensure developers can spot all such methods (functions). We have tried to do this with a `slot_` prefix to the methods we create but it has not been applied uniformly. This PR is intended to conclude helping with this by more rigorously doing so - the names changed herein should all follow a `slot_`*camelCaseMethodName* style. In addition some methods were detected that are not currently used or which are not actually used via the signal/slot system, these have been commented out or have had the prefix removed and the declaration in the relevant header file moved as appropriate. There were two similar sets of (not prefixed with `slot_`) slots in the Profile preferences class that adjusted the colour settings separately for the main console and the mapper the latter had the same names but with a `2` suffix. To made it more clear I have changed them to include `Map` in their names instead. Also, the NON-slot method: `(void) dlgProfilePreferences::setColor(QPushButton*, QColor&, bool)` has been renamed to: `(void) dlgProfilePreferences::setButtonAndProfileColor(QPushButton*, QColor&, bool)` so that it is clearly distinguishable from built in Qt methods that are also called `setColor` - though which do have different signatures! For reference the changes made are: * `TConsole::slot_stop_all_triggers(...)` ==> `TConsole::slot_stopAllItems(...)` * `dlgConnectionProfiles::slot_copy_profile()` ==> `dlgConnectionProfiles::slot_copyProfile()` * `dlgConnectionProfiles::slot_copy_profilesettings_only()` ==> `dlgConnectionProfiles::slot_copyOnlySettingsOfProfile()` * `dlgConnectionProfiles::slot_deleteprofile_check(...)` ==> `dlgConnectionProfiles::slot_deleteProfileCheck(...)` * `dlgConnectionProfiles::slot_password_deleted(...)` ==> `dlgConnectionProfiles::slot_passwordDeleted(...)` * `dlgConnectionProfiles::slot_password_saved(...) ==> `dlgConnectionProfiles::slot_passwordSaved(...)` * `dlgConnectionProfiles::slot_profile_menu(...)` ==> `dlgConnectionProfiles::slot_profileContextMenu(...)` * `dlgConnectionProfiles::slot_reset_custom_icon()` ==> `dlgConnectionProfiles::slot_resetCustomIcon()` * `dlgConnectionProfiles::slot_set_custom_icon()` ==> `dlgConnectionProfiles::slot_setCustomIcon()` * `dlgConnectionProfiles::slot_set_custom_color()` ==> `dlgConnectionProfiles::slot_setCustomColor()` * `dlgConnectionProfiles::slot_update_autologin(...)` ==> `dlgConnectionProfiles::slot_updateAutoConnect(...)` * `dlgConnectionProfiles::slot_update_autoreconnect(...)` ==> `dlgConnectionProfiles::slot_updateAutoReconnect(...)` * `dlgConnectionProfiles::slot_update_description()` ==> `dlgConnectionProfiles::slot_updateDescription()` * `dlgConnectionProfiles::slot_update_discord_optin(...)` ==> `dlgConnectionProfiles::slot_updateDiscordOptIn(...)` * `dlgProfilePreferences::copyMap()` ==> `dlgProfilePreferences::slot_copyMap()` * `dlgProfilePreferences::downloadMap()` ==> `dlgProfilePreferences::slot_downloadMap()` * `dlgProfilePreferences::hideActionLabel()` ==> `dlgProfilePreferences::slot_hideActionLabel()` * `dlgProfilePreferences::loadMap()` ==> `dlgProfilePreferences::slot_loadMap()` * `dlgProfilePreferences::resetColors()` ==> `dlgProfilePreferences::slot_resetColors()` * `dlgProfilePreferences::resetColors2()` ==> `dlgProfilePreferences::slot_resetMapColors()` * `dlgProfilePreferences::saveMap()` ==> `dlgProfilePreferences::slot_saveMap()` * `dlgProfilePreferences::setBgColor()` ==> `dlgProfilePreferences::slot_setBgColor()` * `dlgProfilePreferences::setBgColor2()` ==> `dlgProfilePreferences::slot_setMapBgColor()` * `dlgProfilePreferences::setColorBlack()` ==> `dlgProfilePreferences::slot_setColorBlack()` * `dlgProfilePreferences::setColorBlack2()` ==> `dlgProfilePreferences::slot_setMapColorBlack()` * `dlgProfilePreferences::setColorBlue()` ==> `dlgProfilePreferences::slot_setColorBlue()` * `dlgProfilePreferences::setColorCyan()` ==> `dlgProfilePreferences::slot_setColorCyan()` * `dlgProfilePreferences::setColorBlue2()` ==> `dlgProfilePreferences::slot_setMapColorBlue()` * `dlgProfilePreferences::setColorCyan2()` ==> `dlgProfilePreferences::slot_setMapColorCyan()` * `dlgProfilePreferences::setColorGreen()` ==> `dlgProfilePreferences::slot_setColorGreen()` * `dlgProfilePreferences::setColorGreen2()` ==> `dlgProfilePreferences::slot_setMapColorGreen()` * `dlgProfilePreferences::setColorLightBlack()` ==> `dlgProfilePreferences::slot_setColorLightBlack()` * `dlgProfilePreferences::setColorLightBlack2()` ==> `dlgProfilePreferences::slot_setMapColorLightBlack()` * `dlgProfilePreferences::setColorLightBlue()` ==> `dlgProfilePreferences::slot_setColorLightBlue()` * `dlgProfilePreferences::setColorLightBlue2()` ==> `dlgProfilePreferences::slot_setMapColorLightBlue()` * `dlgProfilePreferences::setColorLightCyan()` ==> `dlgProfilePreferences::slot_setColorLightCyan()` * `dlgProfilePreferences::setColorLightCyan2()` ==> `dlgProfilePreferences::slot_setMapColorLightCyan()` * `dlgProfilePreferences::setColorLightGreen()` ==> `dlgProfilePreferences::slot_setColorLightGreen()` * `dlgProfilePreferences::setColorLightGreen2()` ==> `dlgProfilePreferences::slot_setMapColorLightGreen()` * `dlgProfilePreferences::setColorLightMagenta()` ==> `dlgProfilePreferences::slot_setColorLightMagenta()` * `dlgProfilePreferences::setColorLightMagenta2()` ==> `dlgProfilePreferences::slot_setMapColorLightMagenta()` * `dlgProfilePreferences::setColorLightRed()` ==> `dlgProfilePreferences::slot_setColorLightRed()` * `dlgProfilePreferences::setColorLightRed2()` ==> `dlgProfilePreferences::slot_setMapColorLightRed()` * `dlgProfilePreferences::setColorLightWhite()` ==> `dlgProfilePreferences::slot_setColorLightWhite()` * `dlgProfilePreferences::setColorLightWhite2()` ==> `dlgProfilePreferences::slot_setMapColorLightWhite()` * `dlgProfilePreferences::setColorLightYellow()` ==> `dlgProfilePreferences::slot_setColorLightYellow()` * `dlgProfilePreferences::setColorLightYellow2()` ==> `dlgProfilePreferences::slot_setMapColorLightYellow()` * `dlgProfilePreferences::setColorMagenta()` ==> `dlgProfilePreferences::slot_setColorMagenta()` * `dlgProfilePreferences::setColorMagenta2()` ==> `dlgProfilePreferences::slot_setMapColorMagenta()` * `dlgProfilePreferences::setColorRed2()` ==> `dlgProfilePreferences::slot_setMapColorRed()` * `dlgProfilePreferences::setColorRed()` ==> `dlgProfilePreferences::slot_setColorRed()` * `dlgProfilePreferences::setColorWhite()` ==> `dlgProfilePreferences::slot_setColorWhite()` * `dlgProfilePreferences::setColorWhite2()` ==> `dlgProfilePreferences::slot_setMapColorWhite()` * `dlgProfilePreferences::setColorYellow()` ==> `dlgProfilePreferences::slot_setColorYellow()` * `dlgProfilePreferences::setColorYellow2()` ==> `dlgProfilePreferences::slot_setMapColorYellow()` * `dlgProfilePreferences::setCommandBgColor()` ==> `dlgProfilePreferences::slot_setCommandBgColor()` * `dlgProfilePreferences::setCommandFgColor()` ==> `dlgProfilePreferences::slot_setCommandFgColor()` * `dlgProfilePreferences::setCommandLineBgColor()` ==> `dlgProfilePreferences::slot_setCommandLineBgColor()` * `dlgProfilePreferences::setCommandLineFgColor()` ==> `dlgProfilePreferences::slot_setCommandLineFgColor()` * `dlgProfilePreferences::setDisplayFont()` ==> `dlgProfilePreferences::slot_setDisplayFont()` * `dlgProfilePreferences::setFgColor()` ==> `dlgProfilePreferences::slot_setFgColor()` * `dlgProfilePreferences::setFgColor2()` ==> `dlgProfilePreferences::slot_setMapExitsColor()` * `dlgProfilePreferences::setFontSize()` ==> `dlgProfilePreferences::slot_setFontSize()` * `dlgProfilePreferences::setMapInfoBackground()` ==> `dlgProfilePreferences::slot_setMapInfoBgColor()` * `dlgProfilePreferences::setRoomBorderColor()` ==> `dlgProfilePreferences::slot_setMapRoomBorderColor()` * `dlgProfilePreferences::slot_script_selected(...)` ==> `dlgProfilePreferences::slot_scriptSelected(...)` * `dlgProfilePreferences::slot_theme_selected(...)` ==> `dlgProfilePreferences::slot_themeSelected(...)` * `dlgRoomSymbol::colorRejected()` ==> `dlgRoomSymbol::slot_colorRejected()` * `dlgRoomSymbol::colorSelected(...)` ==> `dlgRoomSymbol::slot_colorSelected(...)` * `dlgRoomSymbol::currentColorChanged(...)` ==> `dlgRoomSymbol::slot_currentColorChanged(...)` * `dlgRoomSymbol::openColorSelector()` ==> `dlgRoomSymbol::slot_openColorSelector()` * `dlgRoomSymbol::resetColor()` ==> `dlgRoomSymbol::slot_resetColors()` * `dlgRoomSymbol::updatePreview()` ==> `dlgRoomSymbol::slot_updatePreview()` * `dlgTriggerEditor::slot_show_vars()` ==> `dlgTriggerEditor::slot_showVariables()` * `dlgTriggerEditor::slot_var_changed(...)` ==> `dlgTriggerEditor::slot_variableChanged(...)` * `dlgTriggerEditor::slot_var_selected(...)` ==> `dlgTriggerEditor::slot_variableSelected(...)` * `mudlet::slot_check_manual_update()` ==> `mudlet::slot_manualUpdateCheck()` * `mudlet::slot_close_current_profile()` ==> `mudlet::slot_closeCurrentProfile()` * `mudlet::slot_close_profile_requested(...)` ==> `mudlet::slot_closeProfileRequested(...)` * `mudlet::slot_connection_dlg_finished(...)` ==> `mudlet::slot_connectionDialogueFinished(...)` * `mudlet::slot_module_manager()` ==> `mudlet::slot_moduleManager()` * `mudlet::slot_mudlet_discord()` ==> `mudlet::slot_mudletDiscord()` * `mudlet::slot_multi_view(...)` ==> `mudlet::slot_multiView(const bool state)` * `mudlet::slot_package_manager()` ==> `mudlet::slot_packageManager()` * `mudlet::slot_package_exporter()` ==> `mudlet::slot_packageExporter()` * `mudlet::slot_timer_fires()` ==> `mudlet::slot_timerFires()` * `mudlet::slot_toggle_multi_view()` ==> `mudlet::slot_toggleMultiView()` Also the names for these in particular have been changed to make more sense: * `dlgProfilePreferences::slot_chooseProfilesChanged()` ==> `dlgProfilePreferences::slot_chosenProfilesChanged()` * `dlgProfilePreferences::slot_editor_tab_selected(...)` ==> `dlgProfilePreferences::slot_tabChanged(...)` * `dlgProfilePreferences::slot_passwords_location_changed(...)` ==> `dlgProfilePreferences::slot_passwordStorageLocationChanged(...)` * `dlgProfilePreferences::slot_save_and_exit()` ==> `dlgProfilePreferences::slot_saveAndClose()` * `dlgTriggerEditor::slot_toggleHiddenVar(...)` ==> `dlgTriggerEditor::slot_hideVariable(...)` - changed to distinguish it from `slot_toggleHiddenVariables(...)` * `dlgTriggerEditor::slot_item_selected_save(...)` ==> `dlgTriggerEditor::slot_saveSelectedItem(...)` * `mudlet::slot_discord()` ==> `mudlet::slot_profileDiscord()` - changed to distinguish it from `slot_mudletDiscord()` Not currently used and commented out: * `dlgProfilePreferences::setCommandLineFont()` ==> `dlgProfilePreferences::slot_setCommandLineFont()` Signed-off-by: Stephen Lyons <slysven@virginmedia.com>
2022-08-29 14:27:13 +02:00
void dlgConnectionProfiles::slot_updateAutoReconnect(int state)
2019-01-06 06:29:16 -05:00
{
QListWidgetItem* pItem = listWidget_profiles->currentItem();
2019-01-06 06:29:16 -05:00
if (!pItem) {
return;
}
writeProfileData(pItem->data(csmNameRole).toString(), qsl("autoreconnect"), QString::number(state));
2019-01-06 06:29:16 -05:00
}
Infrastructure: tidy up naming of SLOT methods and their usage - Part 2 (#6255) When Qt's slot/signal system is used to invoke a method there is some overhead - so it makes sense to ensure developers can spot all such methods (functions). We have tried to do this with a `slot_` prefix to the methods we create but it has not been applied uniformly. This PR (and one or more to follow) is intended to help with this by more rigorously doing so - the names changed herein should all follow a `slot_`*camelCaseMethodName* style. In addition some methods were detected that are not currently used or which are not actually used via the signal/slot system, these have been commented out or have had the prefix removed and the declaration in the relevant header file moved as appropriate. For reference the changes made are: * `TLuaInterpreter::slotDeleteSender(...)` ==> `TLuaInterpreter::slot_deleteSender(...)` * `TLuaInterpreter::slotPurge()` ==> `TLuaInterpreter::slot_purge()` * `cTelnet::handle_socket_signal_connected()` ==> `cTelnet::slot_socketConnected()` * `cTelnet::handle_socket_signal_disconnected()` ==> `cTelnet::slot_socketDisconnected()` * `cTelnet::handle_socket_signal_hostFound(...)` ==> `cTelnet::slot_socketHostFound(...)` * `cTelnet::handle_socket_signal_readyRead()` ==> `cTelnet::slot_socketReadyToBeRead()` * `cTelnet::handle_socket_signal_sslError(...)` ==> `cTelnet::slot_socketSslError(...)` * `cTelnet::setDownloadProgress(...)` ==> `cTelnet::slot_setDownloadProgress(...)` * `dlgComposer::cancel()` ==> `dlgComposer::slot_cancel()` * `dlgComposer::save()` ==> `dlgComposer::slot_save()` * `dlgConnectionProfiles::slot_item_clicked(...)` ==> `dlgConnectionProfiles::slot_itemClicked(...)` * `dlgConnectionProfiles::slot_save_name()` ==> `dlgConnectionProfiles::slot_saveName()` * `dlgConnectionProfiles::slot_update_login(...)` ==> `dlgConnectionProfiles::slot_updateLogin(...)` * `dlgConnectionProfiles::slot_update_name(...)` ==> `dlgConnectionProfiles::slot_updateName(...)` * `dlgConnectionProfiles::slot_update_pass(...)` ==> `dlgConnectionProfiles::slot_updatePassword(...)` * `dlgConnectionProfiles::slot_update_port(...)` ==> `dlgConnectionProfiles::slot_updatePort(...)` * `dlgConnectionProfiles::slot_update_SSL_TSL_port(...)` ==> `dlgConnectionProfiles::slot_updateSslTslPort(...)` * `dlgConnectionProfiles::slot_update_url(...)` ==> `dlgConnectionProfiles::slot_updateUrl(...)` * `dlgMapLabel::pickBgColor()` ==> `dlgMapLabel::slot_pickBgColor()` * `dlgMapLabel::pickFgColor()` ==> `dlgMapLabel::slot_pickFgColor()` * `dlgMapLabel::pickFile()` ==> `dlgMapLabel::slot_pickFile()` * `dlgMapLabel::pickFont()` ==> `dlgMapLabel::slot_pickFont()` * `dlgMapLabel::save()` ==> `dlgMapLabel::slot_save()` * `dlgMapLabel::updateControls()` ==> `dlgMapLabel::slot_updateControls()` * `dlgMapLabel::updateControlsVisibility()` ==> `dlgMapLabel::slot_updateControlsVisibility()` Commented out as not being used: * `cTelnet::handle_socket_signal_error()` ==> `cTelnet::slot_socketError()` * `dlgConnectionProfiles::slot_update_website(...) ==> `dlgConnectionProfiles::slot_updateWebsite(const QString& url)` I have my doubts about whether the `dlgMapLabel::save()`/`dlgMapLabel::slot_save()` method is needed at all, as it might be that the `QDialog::accept()` slot could have been inserted into the `QObject::connect(...)` call directly instead...? Signed-off-by: Stephen Lyons <slysven@virginmedia.com>
2022-08-27 10:47:01 +02:00
void dlgConnectionProfiles::slot_updatePort(const QString& ignoreBlank)
{
const QString port = port_entry->text().trimmed();
if (ignoreBlank.isEmpty()) {
validPort = false;
if (offline_button) {
offline_button->setEnabled(false);
offline_button->setAccessibleDescription(btn_connOrLoad_disabled_accessDesc);
}
2019-01-06 06:29:16 -05:00
if (connect_button) {
connect_button->setEnabled(false);
connect_button->setAccessibleDescription(btn_connOrLoad_disabled_accessDesc);
2019-01-06 06:29:16 -05:00
}
2010-03-15 09:37:16 +01:00
return;
}
2010-03-15 09:37:16 +01:00
2019-01-06 06:29:16 -05:00
if (validateProfile()) {
QListWidgetItem* pItem = listWidget_profiles->currentItem();
2019-01-06 06:29:16 -05:00
if (!pItem) {
return;
}
writeProfileData(pItem->data(csmNameRole).toString(), qsl("port"), port);
}
2009-01-24 02:50:22 +01:00
}
Infrastructure: tidy up naming of SLOT methods and their usage - Part 2 (#6255) When Qt's slot/signal system is used to invoke a method there is some overhead - so it makes sense to ensure developers can spot all such methods (functions). We have tried to do this with a `slot_` prefix to the methods we create but it has not been applied uniformly. This PR (and one or more to follow) is intended to help with this by more rigorously doing so - the names changed herein should all follow a `slot_`*camelCaseMethodName* style. In addition some methods were detected that are not currently used or which are not actually used via the signal/slot system, these have been commented out or have had the prefix removed and the declaration in the relevant header file moved as appropriate. For reference the changes made are: * `TLuaInterpreter::slotDeleteSender(...)` ==> `TLuaInterpreter::slot_deleteSender(...)` * `TLuaInterpreter::slotPurge()` ==> `TLuaInterpreter::slot_purge()` * `cTelnet::handle_socket_signal_connected()` ==> `cTelnet::slot_socketConnected()` * `cTelnet::handle_socket_signal_disconnected()` ==> `cTelnet::slot_socketDisconnected()` * `cTelnet::handle_socket_signal_hostFound(...)` ==> `cTelnet::slot_socketHostFound(...)` * `cTelnet::handle_socket_signal_readyRead()` ==> `cTelnet::slot_socketReadyToBeRead()` * `cTelnet::handle_socket_signal_sslError(...)` ==> `cTelnet::slot_socketSslError(...)` * `cTelnet::setDownloadProgress(...)` ==> `cTelnet::slot_setDownloadProgress(...)` * `dlgComposer::cancel()` ==> `dlgComposer::slot_cancel()` * `dlgComposer::save()` ==> `dlgComposer::slot_save()` * `dlgConnectionProfiles::slot_item_clicked(...)` ==> `dlgConnectionProfiles::slot_itemClicked(...)` * `dlgConnectionProfiles::slot_save_name()` ==> `dlgConnectionProfiles::slot_saveName()` * `dlgConnectionProfiles::slot_update_login(...)` ==> `dlgConnectionProfiles::slot_updateLogin(...)` * `dlgConnectionProfiles::slot_update_name(...)` ==> `dlgConnectionProfiles::slot_updateName(...)` * `dlgConnectionProfiles::slot_update_pass(...)` ==> `dlgConnectionProfiles::slot_updatePassword(...)` * `dlgConnectionProfiles::slot_update_port(...)` ==> `dlgConnectionProfiles::slot_updatePort(...)` * `dlgConnectionProfiles::slot_update_SSL_TSL_port(...)` ==> `dlgConnectionProfiles::slot_updateSslTslPort(...)` * `dlgConnectionProfiles::slot_update_url(...)` ==> `dlgConnectionProfiles::slot_updateUrl(...)` * `dlgMapLabel::pickBgColor()` ==> `dlgMapLabel::slot_pickBgColor()` * `dlgMapLabel::pickFgColor()` ==> `dlgMapLabel::slot_pickFgColor()` * `dlgMapLabel::pickFile()` ==> `dlgMapLabel::slot_pickFile()` * `dlgMapLabel::pickFont()` ==> `dlgMapLabel::slot_pickFont()` * `dlgMapLabel::save()` ==> `dlgMapLabel::slot_save()` * `dlgMapLabel::updateControls()` ==> `dlgMapLabel::slot_updateControls()` * `dlgMapLabel::updateControlsVisibility()` ==> `dlgMapLabel::slot_updateControlsVisibility()` Commented out as not being used: * `cTelnet::handle_socket_signal_error()` ==> `cTelnet::slot_socketError()` * `dlgConnectionProfiles::slot_update_website(...) ==> `dlgConnectionProfiles::slot_updateWebsite(const QString& url)` I have my doubts about whether the `dlgMapLabel::save()`/`dlgMapLabel::slot_save()` method is needed at all, as it might be that the `QDialog::accept()` slot could have been inserted into the `QObject::connect(...)` call directly instead...? Signed-off-by: Stephen Lyons <slysven@virginmedia.com>
2022-08-27 10:47:01 +02:00
void dlgConnectionProfiles::slot_updateSslTslPort(int state)
{
2019-01-06 06:29:16 -05:00
if (validateProfile()) {
QListWidgetItem* pItem = listWidget_profiles->currentItem();
2019-01-06 06:29:16 -05:00
if (!pItem) {
return;
2010-01-22 01:45:34 +01:00
}
writeProfileData(pItem->data(csmNameRole).toString(), qsl("ssl_tsl"), QString::number(state));
2010-01-22 01:45:34 +01:00
}
2019-01-06 06:29:16 -05:00
}
Update: remove Midkemia (and add WoTMUD) to list of predefined MUDs (#321) * Update: remove Midkemia (and add WoTMUD) to list of predefined MUDs The I.R.E. MUD Midkemia-online was shutdown on 2016/09/07 and it is not helpful to continue to offer it as a predefined MUD in the connection dialog. This commit removes it from that list and adds a favourite of mine "WoTMUD" to replace it. The logo is a temporary one that I will replace with a similar one that I need to get clearance to use (and release with a GPL2.1+ licence) from the Wheel of Time MUD sysop with whom I am in contact {Vivienne needs to get approval from the creator Flash who is away for a few days...!} The dlgConnectionProfiles.cpp file has quite a few ASCII strings, as appropriate for a file that will in the future be subject to I18n work I took the opportunity to put QStringLiteral(...) wrappers around the QStrings that are used for non-GUI tasks and tr(...) ones that are. I expect it isn't complete but it goes someway towards dealing with THIS particular file - and it should improve very slightly the generation of QStrings that need to be constructed because it means as much of possible of them is done at compile rather than run-time...! In relation to the above there are points in the code where QStrings are compared to "" to see if they do not have any contents - the isEmpty() method is a better way to do the same (it does not involve a string expression) In (void)dlgConnectionProfiles::slot_update_name( const QString ) there are a couple of variables that use a double underscore prefix - that is NOT A Good Idea: such a prefix is RESERVED in both C and C++. For example see: [what-are-the-rules-about-using-an-underscore-in-a-c-identifier](http://stackoverflow.com/questions/228783/what-are-the-rules-about-using-an-underscore-in-a-c-identifier) also a single prefix might be a bit of a problem with libraries... I have renames the offending things there - but I may have used the single underscore prefix myself in the past. *blush* There are some help messages in this class that use an apostrophe in a word contraction - as a matter of readability this is not considered best practice IMHO so I have converted them to the full words. Following experiences in my previous Pull Request I also took a look at the initialisation and use of some member variables; it became clear that: * (QString) dlgConnectionProfiles::mOrigin * (bool) dlgConnectionProfiles::mEditOK were not used and could be simply deleted and that: * (QString) dlgConnectionProfiles::mUnsavedProfileName * (QString) dlgConnectionProfiles::mCurrentProfileEditName did not need to be member variables and could be local to the method that used them, so in (void)dlgConnectionProfiles::slot_save_name(): * (QString) dlgConnectionProfiles::mCurrentProfileEditName becomes local: (QString) currentProfileEditName and in (void) dlgConnectionProfiles::slot_addProfile(): * (QString) dlgConnectionProfiles::mUnsavedProfileName becomes local: (QString) newName also added initialisers for: * (QStringList) dlgConnectionProfiles::mProfileList * (QPushButton *) dlgConnectionProfiles::connect_button * (QLineEditQPushButton *) dlgConnectionProfiles::delete_profile_lineedit * (QPushButton *) dlgConnectionProfiles::delete_button Signed-off-by: Stephen Lyons <slysven@virginmedia.com> * More: two items missed from prior commit In the previous commit I forgot to take out: * a check for Midkemia in the dlgProfilePreferences class constructor that enabled the download map options; * a similar check in (bool)TMap::restore(QString) that asked if the user wanted to download a map if there was not one found. Signed-off-by: Stephen Lyons <slysven@virginmedia.com> * Tweak: Insert MUD supplied Logo/Icon Received an icon as a .jpg that the MUD operators can allow us to licence as GPL2.1+ which I have converted to a .png file. Signed-off-by: Stephen Lyons <slysven@virginmedia.com>
2016-10-15 09:57:17 +01:00
Infrastructure: tidy up naming of SLOT methods and their usage - Part 2 (#6255) When Qt's slot/signal system is used to invoke a method there is some overhead - so it makes sense to ensure developers can spot all such methods (functions). We have tried to do this with a `slot_` prefix to the methods we create but it has not been applied uniformly. This PR (and one or more to follow) is intended to help with this by more rigorously doing so - the names changed herein should all follow a `slot_`*camelCaseMethodName* style. In addition some methods were detected that are not currently used or which are not actually used via the signal/slot system, these have been commented out or have had the prefix removed and the declaration in the relevant header file moved as appropriate. For reference the changes made are: * `TLuaInterpreter::slotDeleteSender(...)` ==> `TLuaInterpreter::slot_deleteSender(...)` * `TLuaInterpreter::slotPurge()` ==> `TLuaInterpreter::slot_purge()` * `cTelnet::handle_socket_signal_connected()` ==> `cTelnet::slot_socketConnected()` * `cTelnet::handle_socket_signal_disconnected()` ==> `cTelnet::slot_socketDisconnected()` * `cTelnet::handle_socket_signal_hostFound(...)` ==> `cTelnet::slot_socketHostFound(...)` * `cTelnet::handle_socket_signal_readyRead()` ==> `cTelnet::slot_socketReadyToBeRead()` * `cTelnet::handle_socket_signal_sslError(...)` ==> `cTelnet::slot_socketSslError(...)` * `cTelnet::setDownloadProgress(...)` ==> `cTelnet::slot_setDownloadProgress(...)` * `dlgComposer::cancel()` ==> `dlgComposer::slot_cancel()` * `dlgComposer::save()` ==> `dlgComposer::slot_save()` * `dlgConnectionProfiles::slot_item_clicked(...)` ==> `dlgConnectionProfiles::slot_itemClicked(...)` * `dlgConnectionProfiles::slot_save_name()` ==> `dlgConnectionProfiles::slot_saveName()` * `dlgConnectionProfiles::slot_update_login(...)` ==> `dlgConnectionProfiles::slot_updateLogin(...)` * `dlgConnectionProfiles::slot_update_name(...)` ==> `dlgConnectionProfiles::slot_updateName(...)` * `dlgConnectionProfiles::slot_update_pass(...)` ==> `dlgConnectionProfiles::slot_updatePassword(...)` * `dlgConnectionProfiles::slot_update_port(...)` ==> `dlgConnectionProfiles::slot_updatePort(...)` * `dlgConnectionProfiles::slot_update_SSL_TSL_port(...)` ==> `dlgConnectionProfiles::slot_updateSslTslPort(...)` * `dlgConnectionProfiles::slot_update_url(...)` ==> `dlgConnectionProfiles::slot_updateUrl(...)` * `dlgMapLabel::pickBgColor()` ==> `dlgMapLabel::slot_pickBgColor()` * `dlgMapLabel::pickFgColor()` ==> `dlgMapLabel::slot_pickFgColor()` * `dlgMapLabel::pickFile()` ==> `dlgMapLabel::slot_pickFile()` * `dlgMapLabel::pickFont()` ==> `dlgMapLabel::slot_pickFont()` * `dlgMapLabel::save()` ==> `dlgMapLabel::slot_save()` * `dlgMapLabel::updateControls()` ==> `dlgMapLabel::slot_updateControls()` * `dlgMapLabel::updateControlsVisibility()` ==> `dlgMapLabel::slot_updateControlsVisibility()` Commented out as not being used: * `cTelnet::handle_socket_signal_error()` ==> `cTelnet::slot_socketError()` * `dlgConnectionProfiles::slot_update_website(...) ==> `dlgConnectionProfiles::slot_updateWebsite(const QString& url)` I have my doubts about whether the `dlgMapLabel::save()`/`dlgMapLabel::slot_save()` method is needed at all, as it might be that the `QDialog::accept()` slot could have been inserted into the `QObject::connect(...)` call directly instead...? Signed-off-by: Stephen Lyons <slysven@virginmedia.com>
2022-08-27 10:47:01 +02:00
void dlgConnectionProfiles::slot_updateName(const QString& newName)
2019-01-06 06:29:16 -05:00
{
Q_UNUSED(newName)
2019-01-06 06:29:16 -05:00
validateProfile();
}
Infrastructure: tidy up naming of SLOT methods and their usage - Part 2 (#6255) When Qt's slot/signal system is used to invoke a method there is some overhead - so it makes sense to ensure developers can spot all such methods (functions). We have tried to do this with a `slot_` prefix to the methods we create but it has not been applied uniformly. This PR (and one or more to follow) is intended to help with this by more rigorously doing so - the names changed herein should all follow a `slot_`*camelCaseMethodName* style. In addition some methods were detected that are not currently used or which are not actually used via the signal/slot system, these have been commented out or have had the prefix removed and the declaration in the relevant header file moved as appropriate. For reference the changes made are: * `TLuaInterpreter::slotDeleteSender(...)` ==> `TLuaInterpreter::slot_deleteSender(...)` * `TLuaInterpreter::slotPurge()` ==> `TLuaInterpreter::slot_purge()` * `cTelnet::handle_socket_signal_connected()` ==> `cTelnet::slot_socketConnected()` * `cTelnet::handle_socket_signal_disconnected()` ==> `cTelnet::slot_socketDisconnected()` * `cTelnet::handle_socket_signal_hostFound(...)` ==> `cTelnet::slot_socketHostFound(...)` * `cTelnet::handle_socket_signal_readyRead()` ==> `cTelnet::slot_socketReadyToBeRead()` * `cTelnet::handle_socket_signal_sslError(...)` ==> `cTelnet::slot_socketSslError(...)` * `cTelnet::setDownloadProgress(...)` ==> `cTelnet::slot_setDownloadProgress(...)` * `dlgComposer::cancel()` ==> `dlgComposer::slot_cancel()` * `dlgComposer::save()` ==> `dlgComposer::slot_save()` * `dlgConnectionProfiles::slot_item_clicked(...)` ==> `dlgConnectionProfiles::slot_itemClicked(...)` * `dlgConnectionProfiles::slot_save_name()` ==> `dlgConnectionProfiles::slot_saveName()` * `dlgConnectionProfiles::slot_update_login(...)` ==> `dlgConnectionProfiles::slot_updateLogin(...)` * `dlgConnectionProfiles::slot_update_name(...)` ==> `dlgConnectionProfiles::slot_updateName(...)` * `dlgConnectionProfiles::slot_update_pass(...)` ==> `dlgConnectionProfiles::slot_updatePassword(...)` * `dlgConnectionProfiles::slot_update_port(...)` ==> `dlgConnectionProfiles::slot_updatePort(...)` * `dlgConnectionProfiles::slot_update_SSL_TSL_port(...)` ==> `dlgConnectionProfiles::slot_updateSslTslPort(...)` * `dlgConnectionProfiles::slot_update_url(...)` ==> `dlgConnectionProfiles::slot_updateUrl(...)` * `dlgMapLabel::pickBgColor()` ==> `dlgMapLabel::slot_pickBgColor()` * `dlgMapLabel::pickFgColor()` ==> `dlgMapLabel::slot_pickFgColor()` * `dlgMapLabel::pickFile()` ==> `dlgMapLabel::slot_pickFile()` * `dlgMapLabel::pickFont()` ==> `dlgMapLabel::slot_pickFont()` * `dlgMapLabel::save()` ==> `dlgMapLabel::slot_save()` * `dlgMapLabel::updateControls()` ==> `dlgMapLabel::slot_updateControls()` * `dlgMapLabel::updateControlsVisibility()` ==> `dlgMapLabel::slot_updateControlsVisibility()` Commented out as not being used: * `cTelnet::handle_socket_signal_error()` ==> `cTelnet::slot_socketError()` * `dlgConnectionProfiles::slot_update_website(...) ==> `dlgConnectionProfiles::slot_updateWebsite(const QString& url)` I have my doubts about whether the `dlgMapLabel::save()`/`dlgMapLabel::slot_save()` method is needed at all, as it might be that the `QDialog::accept()` slot could have been inserted into the `QObject::connect(...)` call directly instead...? Signed-off-by: Stephen Lyons <slysven@virginmedia.com>
2022-08-27 10:47:01 +02:00
void dlgConnectionProfiles::slot_saveName()
{
QListWidgetItem* pItem = listWidget_profiles->currentItem();
const QString newProfileName = profile_name_entry->text().trimmed();
const QString newProfileHost = host_name_entry->text().trimmed();
const QString newProfilePort = port_entry->text().trimmed();
const int newProfileSslTsl = port_ssl_tsl->isChecked() * 2;
validateProfile();
if (!validName || newProfileName.isEmpty() || !pItem) {
2010-03-15 09:37:16 +01:00
return;
}
2010-01-22 01:45:34 +01:00
const QString currentProfileEditName = pItem->data(csmNameRole).toString();
// don't do anything if this was just a normal click, and not an edit of any sort
if (currentProfileEditName == newProfileName) {
return;
}
2025-09-13 14:17:13 +07:00
// Check for orphaned keychain entries when creating a new profile with a name
// that doesn't exist as a directory but might have keychain entries from
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 07:03:30 -04:00
// a previously deleted profile (deleted outside Mudlet interface)
if (mudlet::self()->storingPasswordsSecurely() && currentProfileEditName == tr("new profile name") && !QDir(mudlet::getMudletPath(enums::profileHomePath, newProfileName)).exists()) {
// Check if there are orphaned keychain entries for this profile name
// Use QPointer to safely detect if dialog or credManager is destroyed during async operations
// Create CredentialManager without a parent to avoid destruction when dialog closes
QPointer<dlgConnectionProfiles> safeThis = this;
QPointer<CredentialManager> safeCredManager = new CredentialManager(nullptr);
safeCredManager->retrievePassword(
newProfileName,
"character",
[safeThis, safeCredManager, currentProfileEditName, newProfileName, newProfileHost, newProfilePort, newProfileSslTsl](
bool foundCharacterEntry, const QString& characterPassword, const QString& errorMessage) {
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 07:03:30 -04:00
Q_UNUSED(characterPassword)
Q_UNUSED(errorMessage)
// Check if credManager was destroyed
if (!safeCredManager) {
qWarning() << "dlgConnectionProfiles: CredentialManager destroyed during keychain operation, aborting";
return;
}
// Check if dialog was destroyed during async operation
if (!safeThis) {
qWarning() << "dlgConnectionProfiles: Dialog destroyed during keychain operation, aborting";
safeCredManager->deleteLater();
return;
}
safeCredManager->retrievePassword(
newProfileName,
"proxy",
[safeThis, safeCredManager, currentProfileEditName, newProfileName, newProfileHost, newProfilePort, newProfileSslTsl, foundCharacterEntry](
bool foundProxyEntry, const QString& proxyPassword, const QString& errorMessage) {
Q_UNUSED(proxyPassword)
Q_UNUSED(errorMessage)
// Check if credManager was destroyed
if (!safeCredManager) {
qWarning() << "dlgConnectionProfiles: CredentialManager destroyed during keychain operation, aborting";
return;
}
// Check if dialog was destroyed during async operation
if (!safeThis) {
qWarning() << "dlgConnectionProfiles: Dialog destroyed during keychain operation, aborting";
safeCredManager->deleteLater();
return;
}
// Define a helper lambda to continue after cleanup is done
// CredentialManager only supports one operation at a time, so we must chain removals
// Captures QPointers to safely detect if dialog or credManager has been destroyed
// -- each must be checked for null before use
auto continueAfterCleanup = [safeThis, safeCredManager, currentProfileEditName, newProfileName, newProfileHost, newProfilePort, newProfileSslTsl]() {
// Clean up credManager if still valid
if (safeCredManager) {
safeCredManager->deleteLater();
}
// Final safety check before accessing dialog members
if (!safeThis) {
qWarning() << "dlgConnectionProfiles: Dialog destroyed before continueProfileSave, aborting";
return;
}
// Find the current item by the old profile name instead of using a captured pointer
auto items = safeThis->findData(*safeThis->listWidget_profiles, currentProfileEditName, csmNameRole);
if (items.isEmpty()) {
qWarning() << "dlgConnectionProfiles: Could not find profile item for" << currentProfileEditName << "after async operation";
return;
}
safeThis->continueProfileSave(items.first(), newProfileName, newProfileHost, newProfilePort, newProfileSslTsl);
};
// If we found any orphaned entries, clean them up (chained to avoid lost callbacks)
if (foundCharacterEntry && foundProxyEntry) {
// Both need cleanup - chain them
safeCredManager->removePassword(newProfileName, "character", [safeCredManager, newProfileName, continueAfterCleanup](bool success, const QString& errorMessage) {
if (!success) {
qWarning() << "dlgConnectionProfiles: Failed to clean up orphaned character password for" << newProfileName << ":" << errorMessage;
}
// Check credManager before chaining next operation
if (!safeCredManager) {
qWarning() << "dlgConnectionProfiles: CredentialManager destroyed, cannot clean up proxy password";
continueAfterCleanup();
return;
}
// Now clean up proxy (chained)
safeCredManager->removePassword(newProfileName, "proxy", [newProfileName, continueAfterCleanup](bool proxySuccess, const QString& proxyError) {
if (!proxySuccess) {
qWarning() << "dlgConnectionProfiles: Failed to clean up orphaned proxy password for" << newProfileName << ":" << proxyError;
}
continueAfterCleanup();
});
});
} else if (foundCharacterEntry) {
safeCredManager->removePassword(newProfileName, "character", [newProfileName, continueAfterCleanup](bool success, const QString& errorMessage) {
if (!success) {
qWarning() << "dlgConnectionProfiles: Failed to clean up orphaned character password for" << newProfileName << ":" << errorMessage;
}
continueAfterCleanup();
});
} else if (foundProxyEntry) {
safeCredManager->removePassword(newProfileName, "proxy", [newProfileName, continueAfterCleanup](bool success, const QString& errorMessage) {
if (!success) {
qWarning() << "dlgConnectionProfiles: Failed to clean up orphaned proxy password for" << newProfileName << ":" << errorMessage;
}
continueAfterCleanup();
});
} else {
// No cleanup needed
continueAfterCleanup();
}
});
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 07:03:30 -04:00
});
2025-09-13 14:17:13 +07:00
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 07:03:30 -04:00
return; // Exit here - continueProfileSave will be called from the callback
}
if (mudlet::self()->storingPasswordsSecurely()) {
migrateSecuredPassword(currentProfileEditName, newProfileName);
}
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 07:03:30 -04:00
continueProfileSave(pItem, newProfileName, newProfileHost, newProfilePort, newProfileSslTsl);
}
2025-09-13 14:17:13 +07:00
void dlgConnectionProfiles::continueProfileSave(QListWidgetItem* pItem, const QString& newProfileName, const QString& newProfileHost, const QString& newProfilePort, const int newProfileSslTsl)
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 07:03:30 -04:00
{
const QString currentProfileEditName = pItem->data(csmNameRole).toString();
Revise: select profiles by typing the name, keep profile icons (#4440) Restore the text display of the profile name. This is so that the keyboard selection of profiles beginning with a particular letter works again. This will close issue #4283 . This also sets it to the minimum size trying to hide it by setting the font size to 1 - however that still show something (and a font size of zero is NOT valid). Manipulating the QPalette of the QListWidget does not work well enough in the presence of Styles or StyleSheets to be a viable means of hiding the text either. Also in the `dlgConnection` class: * refactor out some highly repetitive code that sets up the predefined Mud icons. * remove warnings and adopts a change introduced into Qt 5.15 for the QLabel::pixmap() which now requires a Qt::ReturnByValue argument to prevent an obsolete code warning and to force a QPixmap to be returned by value and not reference... * remove a warning about: QListWidget::setItemSelected(QListWidgetItem*,bool) being deprecated and switches to the recommended: QListWidgetItem::setSelected(bool) Edit: the last item above may have become redundant as further commits amalgamated into this PR may have taken out these lines anyhow! Change the custom icon generator so that there is NOT a dependency on the position in the list for the colours used. This means that the same name will always have the same colours - which will help the user to pick out a particular profile as it will always look the same no matter if other profiles are added or removed. Remove the colour gradient in the custom icon as it is not thought to be useful nowadays. Refactor out the colour icon generator to a single method that is called from more than one place. BugFix: restore odd original way of accessing some QPixmaps This was causing failures in some CI platforms using Qt 5.14. or older. Revise: really frob the custom icon generator This is to try and produce as varied but still readable icons when they are generated entirely on the basis of the MUD name - in a reproducible (though Qt do NOT guarantee repeatable qHash() functionality across Qt versions - so they may not stay *that* constant) manner. Refrob: use a table of fixed colours Using only 15 hues but with 3 different saturation level plus 5 grey-scale one should give enough different icons but not generate them too close to each other for those with normal colour vision... Signed-off-by: Stephen Lyons <slysven@virginmedia.com> Co-authored-by: Kebap <kebap_spam@gmx.net>
2020-12-11 17:53:28 +00:00
setItemName(pItem, newProfileName);
const QDir currentPath(mudlet::getMudletPath(enums::profileHomePath, currentProfileEditName));
const QDir dir;
if (currentPath.exists()) {
// CHECKME: previous code specified a path ending in a '/'
QDir parentpath(mudlet::getMudletPath(enums::profilesPath));
if (!parentpath.rename(currentProfileEditName, newProfileName)) {
notificationArea->show();
notificationAreaIconLabelWarning->show();
notificationAreaIconLabelError->hide();
notificationAreaIconLabelInformation->hide();
notificationAreaMessageBox->show();
notificationAreaMessageBox->setText(tr("Could not rename your profile data on the computer."));
}
} else if (!dir.mkpath(mudlet::getMudletPath(enums::profileHomePath, newProfileName))) {
notificationArea->show();
notificationAreaIconLabelWarning->show();
notificationAreaIconLabelError->hide();
notificationAreaIconLabelInformation->hide();
notificationAreaMessageBox->show();
notificationAreaMessageBox->setText(tr("Could not create the new profile folder on your computer."));
}
if (!newProfileHost.isEmpty()) {
slot_updateUrl(newProfileHost);
}
if (!newProfilePort.isEmpty()) {
slot_updatePort(newProfilePort);
}
slot_updateSslTslPort(newProfileSslTsl);
// if this was a previously deleted profile, restore it
auto& settings = *mudlet::self()->mpSettings;
auto deletedDefaultMuds = settings.value(qsl("deletedDefaultMuds"), QStringList()).toStringList();
if (deletedDefaultMuds.contains(newProfileName)) {
deletedDefaultMuds.removeOne(newProfileName);
settings.setValue(qsl("deletedDefaultMuds"), deletedDefaultMuds);
// run fillout_form to re-create the default profile icon and description
fillout_form();
// and re-select the profile since focus is lost
auto pRestoredItems = findData(*listWidget_profiles, newProfileName, csmNameRole);
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 07:03:30 -04:00
Q_ASSERT_X(pRestoredItems.count() == 1, "dlgConnectionProfiles::continueProfileSave", "Couldn't find exactly 1 restored profile to select");
Refactor: remove minute visible name from Connection dialogue icons We have been trying to hide the text associated with the `QListWidgetItems` in the connection dialogue by setting the font size to the minimum of `1` and by setting it's colour to be white. This is not effective when the background is not white - which is likely for a "Dark" desktop environment. The only way to successfully hide the text is, I think, to not have any! However the text was being used programmatically, so the best way to use a `QListWidget` in this mannar is to store the text elsewhere in each item's structure. Fortunately Qt provides for this with the user data functionality which allows multiple data items (based on the `QVariant` class) to be stored within each `QListWidgetItem` using an integer key to denote the type of the data. Ironically the text, icon and other details for each item are ALSO stored in this way - however for non-Qt internal use the lowest integer key that is to be used is `Qt::UserRole` - which I have assigned to the `(const int) dlgConnectionProfiles::csmNameRole` static value. It is quite possible that a redesign of the Connect Profiles dialogue may use this system to store/cache more details about each profile in the future! The only issue with this is the lack of a: `QListWidget::findData(const QVarient&data, int role = Qt::UserRole ...)` method (c.f. `QComboBox::findData(...)`) so I have had to provide a: `(QList<QListWidgetItem*>) findData(const QListWidget&, const QVariant&, const int role = Qt::UserRole) const; method to fill in this gap and to replace the previous `QListWidget::find(...)` that examined each item's text. Also removed local `(QString) profile` from: `(void) dlgConnectionProfiles::slot_item_clicked(QListWidgetItem*)` as it is merely a redundent duplicate of another local `(QString) profile_name`. Renamed local `(QListWidgetItem*) pM` from: `(void) dlgConnectionProfiles::fillout_form()` to: `(QListWidgetItem*) pItem` as that reflects the name used throughout the rest of the class for this type of variable. Changed a `(QLabel*)::setText(tr(""))` call to the more explicit and less stupid `(QLabel*)::clear()`. Similarly changed a `(QWidget*)::setToolTip("")` to a `(QWidget*)::setToolTip(QString())`. Signed-off-by: Stephen Lyons <slysven@virginmedia.com>
2020-07-01 20:59:42 +01:00
Revise: select profiles by typing the name, keep profile icons (#4440) Restore the text display of the profile name. This is so that the keyboard selection of profiles beginning with a particular letter works again. This will close issue #4283 . This also sets it to the minimum size trying to hide it by setting the font size to 1 - however that still show something (and a font size of zero is NOT valid). Manipulating the QPalette of the QListWidget does not work well enough in the presence of Styles or StyleSheets to be a viable means of hiding the text either. Also in the `dlgConnection` class: * refactor out some highly repetitive code that sets up the predefined Mud icons. * remove warnings and adopts a change introduced into Qt 5.15 for the QLabel::pixmap() which now requires a Qt::ReturnByValue argument to prevent an obsolete code warning and to force a QPixmap to be returned by value and not reference... * remove a warning about: QListWidget::setItemSelected(QListWidgetItem*,bool) being deprecated and switches to the recommended: QListWidgetItem::setSelected(bool) Edit: the last item above may have become redundant as further commits amalgamated into this PR may have taken out these lines anyhow! Change the custom icon generator so that there is NOT a dependency on the position in the list for the colours used. This means that the same name will always have the same colours - which will help the user to pick out a particular profile as it will always look the same no matter if other profiles are added or removed. Remove the colour gradient in the custom icon as it is not thought to be useful nowadays. Refactor out the colour icon generator to a single method that is called from more than one place. BugFix: restore odd original way of accessing some QPixmaps This was causing failures in some CI platforms using Qt 5.14. or older. Revise: really frob the custom icon generator This is to try and produce as varied but still readable icons when they are generated entirely on the basis of the MUD name - in a reproducible (though Qt do NOT guarantee repeatable qHash() functionality across Qt versions - so they may not stay *that* constant) manner. Refrob: use a table of fixed colours Using only 15 hues but with 3 different saturation level plus 5 grey-scale one should give enough different icons but not generate them too close to each other for those with normal colour vision... Signed-off-by: Stephen Lyons <slysven@virginmedia.com> Co-authored-by: Kebap <kebap_spam@gmx.net>
2020-12-11 17:53:28 +00:00
// As we are using QAbstractItemView::SingleSelection this will
// automatically unselect the previous item:
listWidget_profiles->setCurrentItem(pRestoredItems.first());
Infrastructure: tidy up naming of SLOT methods and their usage - Part 2 (#6255) When Qt's slot/signal system is used to invoke a method there is some overhead - so it makes sense to ensure developers can spot all such methods (functions). We have tried to do this with a `slot_` prefix to the methods we create but it has not been applied uniformly. This PR (and one or more to follow) is intended to help with this by more rigorously doing so - the names changed herein should all follow a `slot_`*camelCaseMethodName* style. In addition some methods were detected that are not currently used or which are not actually used via the signal/slot system, these have been commented out or have had the prefix removed and the declaration in the relevant header file moved as appropriate. For reference the changes made are: * `TLuaInterpreter::slotDeleteSender(...)` ==> `TLuaInterpreter::slot_deleteSender(...)` * `TLuaInterpreter::slotPurge()` ==> `TLuaInterpreter::slot_purge()` * `cTelnet::handle_socket_signal_connected()` ==> `cTelnet::slot_socketConnected()` * `cTelnet::handle_socket_signal_disconnected()` ==> `cTelnet::slot_socketDisconnected()` * `cTelnet::handle_socket_signal_hostFound(...)` ==> `cTelnet::slot_socketHostFound(...)` * `cTelnet::handle_socket_signal_readyRead()` ==> `cTelnet::slot_socketReadyToBeRead()` * `cTelnet::handle_socket_signal_sslError(...)` ==> `cTelnet::slot_socketSslError(...)` * `cTelnet::setDownloadProgress(...)` ==> `cTelnet::slot_setDownloadProgress(...)` * `dlgComposer::cancel()` ==> `dlgComposer::slot_cancel()` * `dlgComposer::save()` ==> `dlgComposer::slot_save()` * `dlgConnectionProfiles::slot_item_clicked(...)` ==> `dlgConnectionProfiles::slot_itemClicked(...)` * `dlgConnectionProfiles::slot_save_name()` ==> `dlgConnectionProfiles::slot_saveName()` * `dlgConnectionProfiles::slot_update_login(...)` ==> `dlgConnectionProfiles::slot_updateLogin(...)` * `dlgConnectionProfiles::slot_update_name(...)` ==> `dlgConnectionProfiles::slot_updateName(...)` * `dlgConnectionProfiles::slot_update_pass(...)` ==> `dlgConnectionProfiles::slot_updatePassword(...)` * `dlgConnectionProfiles::slot_update_port(...)` ==> `dlgConnectionProfiles::slot_updatePort(...)` * `dlgConnectionProfiles::slot_update_SSL_TSL_port(...)` ==> `dlgConnectionProfiles::slot_updateSslTslPort(...)` * `dlgConnectionProfiles::slot_update_url(...)` ==> `dlgConnectionProfiles::slot_updateUrl(...)` * `dlgMapLabel::pickBgColor()` ==> `dlgMapLabel::slot_pickBgColor()` * `dlgMapLabel::pickFgColor()` ==> `dlgMapLabel::slot_pickFgColor()` * `dlgMapLabel::pickFile()` ==> `dlgMapLabel::slot_pickFile()` * `dlgMapLabel::pickFont()` ==> `dlgMapLabel::slot_pickFont()` * `dlgMapLabel::save()` ==> `dlgMapLabel::slot_save()` * `dlgMapLabel::updateControls()` ==> `dlgMapLabel::slot_updateControls()` * `dlgMapLabel::updateControlsVisibility()` ==> `dlgMapLabel::slot_updateControlsVisibility()` Commented out as not being used: * `cTelnet::handle_socket_signal_error()` ==> `cTelnet::slot_socketError()` * `dlgConnectionProfiles::slot_update_website(...) ==> `dlgConnectionProfiles::slot_updateWebsite(const QString& url)` I have my doubts about whether the `dlgMapLabel::save()`/`dlgMapLabel::slot_save()` method is needed at all, as it might be that the `QDialog::accept()` slot could have been inserted into the `QObject::connect(...)` call directly instead...? Signed-off-by: Stephen Lyons <slysven@virginmedia.com>
2022-08-27 10:47:01 +02:00
slot_itemClicked(pRestoredItems.first());
} else {
Revise: select profiles by typing the name, keep profile icons (#4440) Restore the text display of the profile name. This is so that the keyboard selection of profiles beginning with a particular letter works again. This will close issue #4283 . This also sets it to the minimum size trying to hide it by setting the font size to 1 - however that still show something (and a font size of zero is NOT valid). Manipulating the QPalette of the QListWidget does not work well enough in the presence of Styles or StyleSheets to be a viable means of hiding the text either. Also in the `dlgConnection` class: * refactor out some highly repetitive code that sets up the predefined Mud icons. * remove warnings and adopts a change introduced into Qt 5.15 for the QLabel::pixmap() which now requires a Qt::ReturnByValue argument to prevent an obsolete code warning and to force a QPixmap to be returned by value and not reference... * remove a warning about: QListWidget::setItemSelected(QListWidgetItem*,bool) being deprecated and switches to the recommended: QListWidgetItem::setSelected(bool) Edit: the last item above may have become redundant as further commits amalgamated into this PR may have taken out these lines anyhow! Change the custom icon generator so that there is NOT a dependency on the position in the list for the colours used. This means that the same name will always have the same colours - which will help the user to pick out a particular profile as it will always look the same no matter if other profiles are added or removed. Remove the colour gradient in the custom icon as it is not thought to be useful nowadays. Refactor out the colour icon generator to a single method that is called from more than one place. BugFix: restore odd original way of accessing some QPixmaps This was causing failures in some CI platforms using Qt 5.14. or older. Revise: really frob the custom icon generator This is to try and produce as varied but still readable icons when they are generated entirely on the basis of the MUD name - in a reproducible (though Qt do NOT guarantee repeatable qHash() functionality across Qt versions - so they may not stay *that* constant) manner. Refrob: use a table of fixed colours Using only 15 hues but with 3 different saturation level plus 5 grey-scale one should give enough different icons but not generate them too close to each other for those with normal colour vision... Signed-off-by: Stephen Lyons <slysven@virginmedia.com> Co-authored-by: Kebap <kebap_spam@gmx.net>
2020-12-11 17:53:28 +00:00
setItemName(pItem, newProfileName);
pItem->setIcon(customIcon(newProfileName, std::nullopt));
}
}
2009-01-24 02:50:22 +01:00
bool dlgConnectionProfiles::showingOnlyMyProfiles() const
{
// the tab bar is hidden for dedicated single-game builds, which list their
// own game(s) unfiltered
return !mpTabBar->isHidden() && mpTabBar->currentIndex() == scmMyGamesTab;
}
void dlgConnectionProfiles::slot_activeTabChanged(const int index)
{
mudlet::self()->mpSettings->setValue(qsl("connectionDialogActiveTab"), index);
const auto* pCurrentItem = listWidget_profiles->currentItem();
const QString previousSelection = pCurrentItem ? pCurrentItem->data(csmNameRole).toString() : QString();
fillout_form();
if (previousSelection.isEmpty()) {
return;
}
// keep the same game selected if the newly shown tab also lists it,
// otherwise the automatic selection made by fillout_form() stands
const auto pPreviousItems = findData(*listWidget_profiles, previousSelection, csmNameRole);
if (!pPreviousItems.isEmpty()) {
listWidget_profiles->setCurrentItem(pPreviousItems.first());
}
}
// On a fresh install with no saved profiles the dialog shows the welcome
// message in place of the connection details; swap them back in and undo the
// shrink that was applied to fit the welcome message.
void dlgConnectionProfiles::revealConnectionDetails()
{
if (mProgrammaticProfileSelection || welcome_message->isHidden()) {
return;
}
welcome_message->hide();
tabWidget_connectionInfo->show();
informationArea->show();
if (mDialogHeightBeforeShrink > height()) {
resize(width(), mDialogHeightBeforeShrink);
}
}
2009-01-24 02:50:22 +01:00
void dlgConnectionProfiles::slot_addProfile()
{
2013-06-12 21:44:38 +02:00
profile_name_entry->setReadOnly(false);
// while normally handled by fillout_form, due to it's asynchronous nature it is better UX to reset it here
// Block signals to prevent triggering password save for the previously selected profile
{
const QSignalBlocker blocker(character_password_entry);
character_password_entry->setText(QString());
}
dismissTutorialInvitation();
fillout_form();
revealConnectionDetails();
2010-03-15 09:37:16 +01:00
const QString newname = tr("new profile name");
2010-03-15 09:37:16 +01:00
auto pItem = new (std::nothrow) QListWidgetItem();
if (!pItem) {
return;
}
Revise: select profiles by typing the name, keep profile icons (#4440) Restore the text display of the profile name. This is so that the keyboard selection of profiles beginning with a particular letter works again. This will close issue #4283 . This also sets it to the minimum size trying to hide it by setting the font size to 1 - however that still show something (and a font size of zero is NOT valid). Manipulating the QPalette of the QListWidget does not work well enough in the presence of Styles or StyleSheets to be a viable means of hiding the text either. Also in the `dlgConnection` class: * refactor out some highly repetitive code that sets up the predefined Mud icons. * remove warnings and adopts a change introduced into Qt 5.15 for the QLabel::pixmap() which now requires a Qt::ReturnByValue argument to prevent an obsolete code warning and to force a QPixmap to be returned by value and not reference... * remove a warning about: QListWidget::setItemSelected(QListWidgetItem*,bool) being deprecated and switches to the recommended: QListWidgetItem::setSelected(bool) Edit: the last item above may have become redundant as further commits amalgamated into this PR may have taken out these lines anyhow! Change the custom icon generator so that there is NOT a dependency on the position in the list for the colours used. This means that the same name will always have the same colours - which will help the user to pick out a particular profile as it will always look the same no matter if other profiles are added or removed. Remove the colour gradient in the custom icon as it is not thought to be useful nowadays. Refactor out the colour icon generator to a single method that is called from more than one place. BugFix: restore odd original way of accessing some QPixmaps This was causing failures in some CI platforms using Qt 5.14. or older. Revise: really frob the custom icon generator This is to try and produce as varied but still readable icons when they are generated entirely on the basis of the MUD name - in a reproducible (though Qt do NOT guarantee repeatable qHash() functionality across Qt versions - so they may not stay *that* constant) manner. Refrob: use a table of fixed colours Using only 15 hues but with 3 different saturation level plus 5 grey-scale one should give enough different icons but not generate them too close to each other for those with normal colour vision... Signed-off-by: Stephen Lyons <slysven@virginmedia.com> Co-authored-by: Kebap <kebap_spam@gmx.net>
2020-12-11 17:53:28 +00:00
setItemName(pItem, newname);
// without an icon the item is an invisible blank in the list
pItem->setIcon(customIcon(newname, std::nullopt));
2010-03-15 09:37:16 +01:00
// insert the new entry at the top of the list - appending would bury it
// at the bottom, below all the predefined games
listWidget_profiles->insertItem(0, pItem);
2010-03-15 09:37:16 +01:00
Revise: select profiles by typing the name, keep profile icons (#4440) Restore the text display of the profile name. This is so that the keyboard selection of profiles beginning with a particular letter works again. This will close issue #4283 . This also sets it to the minimum size trying to hide it by setting the font size to 1 - however that still show something (and a font size of zero is NOT valid). Manipulating the QPalette of the QListWidget does not work well enough in the presence of Styles or StyleSheets to be a viable means of hiding the text either. Also in the `dlgConnection` class: * refactor out some highly repetitive code that sets up the predefined Mud icons. * remove warnings and adopts a change introduced into Qt 5.15 for the QLabel::pixmap() which now requires a Qt::ReturnByValue argument to prevent an obsolete code warning and to force a QPixmap to be returned by value and not reference... * remove a warning about: QListWidget::setItemSelected(QListWidgetItem*,bool) being deprecated and switches to the recommended: QListWidgetItem::setSelected(bool) Edit: the last item above may have become redundant as further commits amalgamated into this PR may have taken out these lines anyhow! Change the custom icon generator so that there is NOT a dependency on the position in the list for the colours used. This means that the same name will always have the same colours - which will help the user to pick out a particular profile as it will always look the same no matter if other profiles are added or removed. Remove the colour gradient in the custom icon as it is not thought to be useful nowadays. Refactor out the colour icon generator to a single method that is called from more than one place. BugFix: restore odd original way of accessing some QPixmaps This was causing failures in some CI platforms using Qt 5.14. or older. Revise: really frob the custom icon generator This is to try and produce as varied but still readable icons when they are generated entirely on the basis of the MUD name - in a reproducible (though Qt do NOT guarantee repeatable qHash() functionality across Qt versions - so they may not stay *that* constant) manner. Refrob: use a table of fixed colours Using only 15 hues but with 3 different saturation level plus 5 grey-scale one should give enough different icons but not generate them too close to each other for those with normal colour vision... Signed-off-by: Stephen Lyons <slysven@virginmedia.com> Co-authored-by: Kebap <kebap_spam@gmx.net>
2020-12-11 17:53:28 +00:00
// As we are using QAbstractItemView::SingleSelection this will
// automatically unselect the previous item, and auto-scroll brings the
// new item into view:
listWidget_profiles->setCurrentItem(pItem);
2010-03-15 09:37:16 +01:00
profile_name_entry->setText(newname);
profile_name_entry->setFocus();
profile_name_entry->selectAll();
profile_name_entry->setReadOnly(false);
host_name_entry->setReadOnly(false);
port_entry->setReadOnly(false);
2010-03-15 09:37:16 +01:00
validName = false;
validUrl = false;
validPort = false;
offline_button->setEnabled(false);
offline_button->setAccessibleDescription(btn_connOrLoad_disabled_accessDesc);
connect_button->setEnabled(false);
connect_button->setAccessibleDescription(btn_connOrLoad_disabled_accessDesc);
2009-01-24 02:50:22 +01:00
}
void dlgConnectionProfiles::showRemovalProblem(const QString& message)
{
notificationArea->show();
notificationAreaIconLabelWarning->show();
notificationAreaIconLabelError->hide();
notificationAreaIconLabelInformation->hide();
notificationAreaMessageBox->show();
notificationAreaMessageBox->setText(message);
}
void dlgConnectionProfiles::reallyDeleteProfile(const QString& profile)
{
const QString profilesPath = mudlet::getMudletPath(enums::profilesPath);
const QString profileFolder = profileFolderPath(profilesPath, profile);
if (profileFolder.isEmpty()) {
qWarning().nospace() << "dlgConnectionProfiles::reallyDeleteProfile(\"" << profile << "\") ERROR - refusing to delete: that name does not address a folder inside \"" << profilesPath << "\".";
// rebuild the list first: it re-selects a profile, and that clears the
// notification area on its way through validateProfile()
fillout_form();
//: %1 is a profile name that does not name a folder of its own, so there is nothing that could be removed for it
showRemovalProblem(tr("'%1' has no profile folder of its own, so there is nothing to remove.").arg(profile));
return;
}
QDir dir(profileFolder);
if (!dir.removeRecursively()) {
// the profile is still on disk, so its password and its list entry stay:
// removing either would strand the data that is left
qWarning().nospace() << "dlgConnectionProfiles::reallyDeleteProfile(\"" << profile << "\") ERROR - could not completely remove \"" << profileFolder << "\".";
fillout_form();
//: %1 is a profile name. Shown when some of the profile's files could not be deleted, e.g. because another program has them open
showRemovalProblem(tr("Could not remove everything belonging to '%1'. Close it if it is open elsewhere, check that you may write to its folder, and try again.").arg(profile));
return;
}
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 07:03:30 -04:00
// Clean up keychain entries for the deleted profile
// Note: CredentialManager only supports one operation at a time, so we must
// chain the operations - the second removal starts only after the first completes.
// This prevents lost callbacks from aborting in-progress keychain operations.
// Crash prevention comes from parentless CredentialManager + QPointer guards.
// Create CredentialManager without a parent to avoid destruction when dialog closes
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 07:03:30 -04:00
if (mudlet::self()->storingPasswordsSecurely()) {
QPointer<CredentialManager> safeCredManager = new CredentialManager(nullptr);
2025-09-13 14:17:13 +07:00
// Clean up character password entry first, then chain proxy cleanup
safeCredManager->removePassword(profile, "character", [safeCredManager, profile](bool success, const QString& errorMessage) {
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 07:03:30 -04:00
if (!success) {
qWarning() << "dlgConnectionProfiles: Failed to clean up character password for deleted profile" << profile << ":" << errorMessage;
}
2025-09-13 14:17:13 +07:00
// Check if credManager was destroyed before chaining next operation
if (!safeCredManager) {
qWarning() << "dlgConnectionProfiles: CredentialManager destroyed, cannot clean up proxy password";
return;
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 07:03:30 -04:00
}
2025-09-13 14:17:13 +07:00
// Now clean up proxy password entry (chained after character password removal completes)
safeCredManager->removePassword(profile, "proxy", [safeCredManager, profile](bool proxySuccess, const QString& proxyErrorMessage) {
if (!proxySuccess) {
qWarning() << "dlgConnectionProfiles: Failed to clean up proxy password for deleted profile" << profile << ":" << proxyErrorMessage;
}
// Clean up the credential manager after both operations complete (if still valid)
if (safeCredManager) {
safeCredManager->deleteLater();
}
});
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 07:03:30 -04:00
});
}
// record the deletion; the games catalog deliberately ignores this list
// now - only the self-test entry in fillout_form() still honours it, and
// continueProfileSave() clears the entry on profile re-creation
auto& settings = *mudlet::self()->mpSettings;
auto deletedDefaultMuds = settings.value(qsl("deletedDefaultMuds"), QStringList()).toStringList();
if (!deletedDefaultMuds.contains(profile)) {
deletedDefaultMuds.append(profile);
}
settings.setValue(qsl("deletedDefaultMuds"), deletedDefaultMuds);
fillout_form();
listWidget_profiles->setFocus();
2009-03-15 02:58:00 +01:00
}
// called when the 'delete' button is pressed, raises a dialog to confirm deletion
// if this profile has been used
2009-01-24 02:50:22 +01:00
void dlgConnectionProfiles::slot_deleteProfile()
{
if (!listWidget_profiles->currentItem()) {
return;
}
2010-03-15 09:37:16 +01:00
const QString profile = listWidget_profiles->currentItem()->data(csmNameRole).toString();
const QStringList& onlyShownPredefinedProfiles{mudlet::self()->mOnlyShownPredefinedProfiles};
if (!onlyShownPredefinedProfiles.isEmpty() && onlyShownPredefinedProfiles.contains(profile)) {
// Do NOT allow deletion of the prioritised predefined MUD:
return;
}
2010-03-15 09:37:16 +01:00
const QDir profileDir(mudlet::getMudletPath(enums::profileHomePath, profile));
bool nothingToLose = !profileDir.exists() || profileDir.entryList(QDir::Dirs | QDir::Hidden | QDir::NoDotAndDotDot).isEmpty();
if (nothingToLose) {
for (const QString& fileName : profileDir.entryList(QDir::Files | QDir::Hidden)) {
if (!scmConnectionDetailFiles.contains(fileName)) {
nothingToLose = false;
break;
}
}
}
if (nothingToLose) {
reallyDeleteProfile(profile);
return;
}
QUiLoader loader;
2010-03-15 09:37:16 +01:00
QFile file(qsl(":/ui/delete_profile_confirmation.ui"));
if (!file.open(QFile::ReadOnly)) {
qWarning() << "dlgConnectionProfiles: failed to open UI file for reading:" << file.errorString();
return;
}
auto* delete_profile_dialog = dynamic_cast<QDialog*>(loader.load(&file, this));
file.close();
if (!delete_profile_dialog) {
qWarning() << "dlgConnectionProfiles::slot_deleteProfile() ERROR - the deletion confirmation did not load as a dialog.";
//: %1 is a profile name. Shown when the dialog asking the user to confirm a removal could not be built
showRemovalProblem(tr("Could not open the confirmation, so '%1' has not been removed.").arg(profile));
return;
}
auto* nameEntry = delete_profile_dialog->findChild<QLineEdit*>(qsl("delete_profile_lineedit"));
auto* deleteButton = delete_profile_dialog->findChild<QPushButton*>(qsl("delete_button"));
auto* cancelButton = delete_profile_dialog->findChild<QPushButton*>(qsl("cancel_button"));
if (!nameEntry || !deleteButton || !cancelButton) {
qWarning() << "dlgConnectionProfiles::slot_deleteProfile() ERROR - the deletion confirmation is missing one of its widgets.";
showRemovalProblem(tr("Could not open the confirmation, so '%1' has not been removed.").arg(profile));
delete delete_profile_dialog;
return;
}
// The confirmation is not modal, so by the time it is answered the selection
// may have moved on, or a second confirmation may be open alongside it:
connect(nameEntry, &QLineEdit::textChanged, delete_profile_dialog, [deleteButton, profile](const QString& text) {
deleteButton->setEnabled(text == profile);
if (deleteButton->isEnabled()) {
deleteButton->setFocus();
}
});
connect(delete_profile_dialog, &QDialog::accepted, this, [this, profile]() {
reallyDeleteProfile(profile);
});
nameEntry->setPlaceholderText(profile);
nameEntry->setFocus();
deleteButton->setEnabled(false);
delete_profile_dialog->setWindowTitle(tr("Deleting '%1'").arg(profile));
delete_profile_dialog->setAttribute(Qt::WA_DeleteOnClose);
delete_profile_dialog->show();
delete_profile_dialog->raise();
2009-01-24 02:50:22 +01:00
}
QString dlgConnectionProfiles::readProfileData(const QString& profile, const QString& item) const
{
QFile file(mudlet::getMudletPath(enums::profileDataItemPath, profile, item));
const bool success = file.open(QIODevice::ReadOnly);
QString ret;
if (success) {
QDataStream ifs(&file);
BugFix ameliorate Qt changes to binary formats for QDataStream (#3133) * BugFix ameliorate Qt changes to binary formats for QDataStream In other words - fix https://github.com/Mudlet/Mudlet/issues/3088 ...! This forces the binary file format to be that which is used for Qt 5.12 if the run-time version of the Qt libraries are Qt 5.13 or later. This is needed to handle a change in the format that QFonts are saved/loaded in a binary form in a QDataStream but it also clamps the binary format to be equivalent to QDataStream::Qt_5_12 everywhere I could see it used so that future Mudlet versions do not suffer further issues going forward when Qt revise the QDataStream format for the classes it handles. This should also close https://github.com/Mudlet/Mudlet/issues/785 ! NOTE: THIS WILL BREAK THINGS TEMPORARILY FOR USERS OF MUDLET VERSIONS AFTER 4.0.1 OR THOSE WHO HAVE MANUALLY SET THE FILE FORMAT ON THEIR MAP TO BE VERSION 19 OR HIGHER AND HAVE MOVED BETWEEN A MUDLET USING A RUN-TIME QT VERSION LESS THAN QT 5.13 AND ONE USING THAT OR LATER. IT WILL LIKELY CAUSE EXISTING MAP FILE CONTENTS TO BECOME GARBAGE WHEN READ BY A MUDLET VERSION INCLUDING THIS PULL-REQUEST - SO IT IS NECESSARY TO OPEN ANY WANTED VERSION 19 OR LATER MAP FILES IN THE CURRENT (BUGGY) QT 5.13 OR LATER USING MUDLET AND RESAVE IT IN MUDLET FILE FORMAT 18 BEFORE UPGRADING TO A MUDLET WITH THIS PULL-REQUEST INCLUDED. ONCE THE MAP IS THEN LOADED IN THE NEWER MUDLET IT CAN BE RESET TO THE LATEST MAP FORMAT - and we should avoid similar Qt library change induced problems in the future. Signed-off-by: Stephen Lyons <slysven@virginmedia.com>
2019-09-29 23:41:58 +02:00
if (mudlet::scmRunTimeQtVersion >= QVersionNumber(5, 13, 0)) {
ifs.setVersion(mudlet::scmQDataStreamFormat_5_12);
}
ifs >> ret;
file.close();
}
return ret;
}
// A new item here may need adding to scmConnectionDetailFiles above. Unlike
// mudlet::writeProfileData() this does not create the profile's folder, so a
// write before there is one is quietly dropped.
Improve first-time user experience (#1016) * Fixed the welcome message to show again It didn't before because default_host profile directory was getting created on the disk before the check. * Made it so a random profile gets selected for a first-time launch * Modernized mudlet::startAutoLogin function * Renamed poorly-named method to open connections dialog It also was confusing with Host::connectToServer doing the actual connecting * Made the connection dialog open if no profiles are on autoload * Removed superseded status bar notification The connection dialog now opens automatically. * Improved first-launch Mudlet size and positioning Mudlet is now centered and has a decent size for the most common resolutions * Fix connection dialog to get a decent size by default * Updated the welcome text It was pretty ancient and referenced outdated button names. Also cut down on the wording so a new user isn't hit with a wall of text. * Moved icon over to left so the screen isn't so busy * Undid hardcoded fonts and sizes in connection dialog User-selected desktop fonts and sizes should be used instead. * Fixed connection dialog to actually select the last played profile There was a bug where it would ignore predefined profiles in the calculation. Also use the last modified, not read date - a lot of things like virus scanners could read the directory whereas far fewer things will write to it. * Moved 'loaded profile' warning to be below profiles This prevents the whole thing from jumping around. * Added a margin to the right for aesthetic reasons * Revised to use getDescription as a method * Got tooltips to show on profile icons
2017-06-04 08:13:51 +02:00
QPair<bool, QString> dlgConnectionProfiles::writeProfileData(const QString& profile, const QString& item, const QString& what)
{
QSaveFile file(mudlet::getMudletPath(enums::profileDataItemPath, profile, item));
Improve first-time user experience (#1016) * Fixed the welcome message to show again It didn't before because default_host profile directory was getting created on the disk before the check. * Made it so a random profile gets selected for a first-time launch * Modernized mudlet::startAutoLogin function * Renamed poorly-named method to open connections dialog It also was confusing with Host::connectToServer doing the actual connecting * Made the connection dialog open if no profiles are on autoload * Removed superseded status bar notification The connection dialog now opens automatically. * Improved first-launch Mudlet size and positioning Mudlet is now centered and has a decent size for the most common resolutions * Fix connection dialog to get a decent size by default * Updated the welcome text It was pretty ancient and referenced outdated button names. Also cut down on the wording so a new user isn't hit with a wall of text. * Moved icon over to left so the screen isn't so busy * Undid hardcoded fonts and sizes in connection dialog User-selected desktop fonts and sizes should be used instead. * Fixed connection dialog to actually select the last played profile There was a bug where it would ignore predefined profiles in the calculation. Also use the last modified, not read date - a lot of things like virus scanners could read the directory whereas far fewer things will write to it. * Moved 'loaded profile' warning to be below profiles This prevents the whole thing from jumping around. * Added a margin to the right for aesthetic reasons * Revised to use getDescription as a method * Got tooltips to show on profile icons
2017-06-04 08:13:51 +02:00
if (file.open(QIODevice::WriteOnly | QIODevice::Unbuffered)) {
QDataStream ofs(&file);
BugFix ameliorate Qt changes to binary formats for QDataStream (#3133) * BugFix ameliorate Qt changes to binary formats for QDataStream In other words - fix https://github.com/Mudlet/Mudlet/issues/3088 ...! This forces the binary file format to be that which is used for Qt 5.12 if the run-time version of the Qt libraries are Qt 5.13 or later. This is needed to handle a change in the format that QFonts are saved/loaded in a binary form in a QDataStream but it also clamps the binary format to be equivalent to QDataStream::Qt_5_12 everywhere I could see it used so that future Mudlet versions do not suffer further issues going forward when Qt revise the QDataStream format for the classes it handles. This should also close https://github.com/Mudlet/Mudlet/issues/785 ! NOTE: THIS WILL BREAK THINGS TEMPORARILY FOR USERS OF MUDLET VERSIONS AFTER 4.0.1 OR THOSE WHO HAVE MANUALLY SET THE FILE FORMAT ON THEIR MAP TO BE VERSION 19 OR HIGHER AND HAVE MOVED BETWEEN A MUDLET USING A RUN-TIME QT VERSION LESS THAN QT 5.13 AND ONE USING THAT OR LATER. IT WILL LIKELY CAUSE EXISTING MAP FILE CONTENTS TO BECOME GARBAGE WHEN READ BY A MUDLET VERSION INCLUDING THIS PULL-REQUEST - SO IT IS NECESSARY TO OPEN ANY WANTED VERSION 19 OR LATER MAP FILES IN THE CURRENT (BUGGY) QT 5.13 OR LATER USING MUDLET AND RESAVE IT IN MUDLET FILE FORMAT 18 BEFORE UPGRADING TO A MUDLET WITH THIS PULL-REQUEST INCLUDED. ONCE THE MAP IS THEN LOADED IN THE NEWER MUDLET IT CAN BE RESET TO THE LATEST MAP FORMAT - and we should avoid similar Qt library change induced problems in the future. Signed-off-by: Stephen Lyons <slysven@virginmedia.com>
2019-09-29 23:41:58 +02:00
if (mudlet::scmRunTimeQtVersion >= QVersionNumber(5, 13, 0)) {
ofs.setVersion(mudlet::scmQDataStreamFormat_5_12);
}
Improve first-time user experience (#1016) * Fixed the welcome message to show again It didn't before because default_host profile directory was getting created on the disk before the check. * Made it so a random profile gets selected for a first-time launch * Modernized mudlet::startAutoLogin function * Renamed poorly-named method to open connections dialog It also was confusing with Host::connectToServer doing the actual connecting * Made the connection dialog open if no profiles are on autoload * Removed superseded status bar notification The connection dialog now opens automatically. * Improved first-launch Mudlet size and positioning Mudlet is now centered and has a decent size for the most common resolutions * Fix connection dialog to get a decent size by default * Updated the welcome text It was pretty ancient and referenced outdated button names. Also cut down on the wording so a new user isn't hit with a wall of text. * Moved icon over to left so the screen isn't so busy * Undid hardcoded fonts and sizes in connection dialog User-selected desktop fonts and sizes should be used instead. * Fixed connection dialog to actually select the last played profile There was a bug where it would ignore predefined profiles in the calculation. Also use the last modified, not read date - a lot of things like virus scanners could read the directory whereas far fewer things will write to it. * Moved 'loaded profile' warning to be below profiles This prevents the whole thing from jumping around. * Added a margin to the right for aesthetic reasons * Revised to use getDescription as a method * Got tooltips to show on profile icons
2017-06-04 08:13:51 +02:00
ofs << what;
if (!file.commit()) {
qDebug().noquote().nospace() << "dlgConnectionProfiles::writeProfileData(...) ERROR - writing profile: \"" << profile << "\", item: \"" << item << "\", reason: \"" << file.errorString()
<< "\".";
}
Improve first-time user experience (#1016) * Fixed the welcome message to show again It didn't before because default_host profile directory was getting created on the disk before the check. * Made it so a random profile gets selected for a first-time launch * Modernized mudlet::startAutoLogin function * Renamed poorly-named method to open connections dialog It also was confusing with Host::connectToServer doing the actual connecting * Made the connection dialog open if no profiles are on autoload * Removed superseded status bar notification The connection dialog now opens automatically. * Improved first-launch Mudlet size and positioning Mudlet is now centered and has a decent size for the most common resolutions * Fix connection dialog to get a decent size by default * Updated the welcome text It was pretty ancient and referenced outdated button names. Also cut down on the wording so a new user isn't hit with a wall of text. * Moved icon over to left so the screen isn't so busy * Undid hardcoded fonts and sizes in connection dialog User-selected desktop fonts and sizes should be used instead. * Fixed connection dialog to actually select the last played profile There was a bug where it would ignore predefined profiles in the calculation. Also use the last modified, not read date - a lot of things like virus scanners could read the directory whereas far fewer things will write to it. * Moved 'loaded profile' warning to be below profiles This prevents the whole thing from jumping around. * Added a margin to the right for aesthetic reasons * Revised to use getDescription as a method * Got tooltips to show on profile icons
2017-06-04 08:13:51 +02:00
}
if (file.error() == QFileDevice::NoError) {
return {true, QString()};
Improve first-time user experience (#1016) * Fixed the welcome message to show again It didn't before because default_host profile directory was getting created on the disk before the check. * Made it so a random profile gets selected for a first-time launch * Modernized mudlet::startAutoLogin function * Renamed poorly-named method to open connections dialog It also was confusing with Host::connectToServer doing the actual connecting * Made the connection dialog open if no profiles are on autoload * Removed superseded status bar notification The connection dialog now opens automatically. * Improved first-launch Mudlet size and positioning Mudlet is now centered and has a decent size for the most common resolutions * Fix connection dialog to get a decent size by default * Updated the welcome text It was pretty ancient and referenced outdated button names. Also cut down on the wording so a new user isn't hit with a wall of text. * Moved icon over to left so the screen isn't so busy * Undid hardcoded fonts and sizes in connection dialog User-selected desktop fonts and sizes should be used instead. * Fixed connection dialog to actually select the last played profile There was a bug where it would ignore predefined profiles in the calculation. Also use the last modified, not read date - a lot of things like virus scanners could read the directory whereas far fewer things will write to it. * Moved 'loaded profile' warning to be below profiles This prevents the whole thing from jumping around. * Added a margin to the right for aesthetic reasons * Revised to use getDescription as a method * Got tooltips to show on profile icons
2017-06-04 08:13:51 +02:00
}
return {false, file.errorString()};
}
QString dlgConnectionProfiles::getDescription(const QString& profile_name) const
Improve first-time user experience (#1016) * Fixed the welcome message to show again It didn't before because default_host profile directory was getting created on the disk before the check. * Made it so a random profile gets selected for a first-time launch * Modernized mudlet::startAutoLogin function * Renamed poorly-named method to open connections dialog It also was confusing with Host::connectToServer doing the actual connecting * Made the connection dialog open if no profiles are on autoload * Removed superseded status bar notification The connection dialog now opens automatically. * Improved first-launch Mudlet size and positioning Mudlet is now centered and has a decent size for the most common resolutions * Fix connection dialog to get a decent size by default * Updated the welcome text It was pretty ancient and referenced outdated button names. Also cut down on the wording so a new user isn't hit with a wall of text. * Moved icon over to left so the screen isn't so busy * Undid hardcoded fonts and sizes in connection dialog User-selected desktop fonts and sizes should be used instead. * Fixed connection dialog to actually select the last played profile There was a bug where it would ignore predefined profiles in the calculation. Also use the last modified, not read date - a lot of things like virus scanners could read the directory whereas far fewer things will write to it. * Moved 'loaded profile' warning to be below profiles This prevents the whole thing from jumping around. * Added a margin to the right for aesthetic reasons * Revised to use getDescription as a method * Got tooltips to show on profile icons
2017-06-04 08:13:51 +02:00
{
QString profileDesc = readProfileData(profile_name, qsl("description"));
if (profileDesc.isEmpty()) {
auto itDetails = TGameDetails::findGame(profile_name);
if (itDetails != TGameDetails::scmDefaultGames.constEnd()) {
if (!(*itDetails).description.isEmpty()) {
return (*itDetails).description;
}
Improve first-time user experience (#1016) * Fixed the welcome message to show again It didn't before because default_host profile directory was getting created on the disk before the check. * Made it so a random profile gets selected for a first-time launch * Modernized mudlet::startAutoLogin function * Renamed poorly-named method to open connections dialog It also was confusing with Host::connectToServer doing the actual connecting * Made the connection dialog open if no profiles are on autoload * Removed superseded status bar notification The connection dialog now opens automatically. * Improved first-launch Mudlet size and positioning Mudlet is now centered and has a decent size for the most common resolutions * Fix connection dialog to get a decent size by default * Updated the welcome text It was pretty ancient and referenced outdated button names. Also cut down on the wording so a new user isn't hit with a wall of text. * Moved icon over to left so the screen isn't so busy * Undid hardcoded fonts and sizes in connection dialog User-selected desktop fonts and sizes should be used instead. * Fixed connection dialog to actually select the last played profile There was a bug where it would ignore predefined profiles in the calculation. Also use the last modified, not read date - a lot of things like virus scanners could read the directory whereas far fewer things will write to it. * Moved 'loaded profile' warning to be below profiles This prevents the whole thing from jumping around. * Added a margin to the right for aesthetic reasons * Revised to use getDescription as a method * Got tooltips to show on profile icons
2017-06-04 08:13:51 +02:00
}
}
return profileDesc;
Improve first-time user experience (#1016) * Fixed the welcome message to show again It didn't before because default_host profile directory was getting created on the disk before the check. * Made it so a random profile gets selected for a first-time launch * Modernized mudlet::startAutoLogin function * Renamed poorly-named method to open connections dialog It also was confusing with Host::connectToServer doing the actual connecting * Made the connection dialog open if no profiles are on autoload * Removed superseded status bar notification The connection dialog now opens automatically. * Improved first-launch Mudlet size and positioning Mudlet is now centered and has a decent size for the most common resolutions * Fix connection dialog to get a decent size by default * Updated the welcome text It was pretty ancient and referenced outdated button names. Also cut down on the wording so a new user isn't hit with a wall of text. * Moved icon over to left so the screen isn't so busy * Undid hardcoded fonts and sizes in connection dialog User-selected desktop fonts and sizes should be used instead. * Fixed connection dialog to actually select the last played profile There was a bug where it would ignore predefined profiles in the calculation. Also use the last modified, not read date - a lot of things like virus scanners could read the directory whereas far fewer things will write to it. * Moved 'loaded profile' warning to be below profiles This prevents the whole thing from jumping around. * Added a margin to the right for aesthetic reasons * Revised to use getDescription as a method * Got tooltips to show on profile icons
2017-06-04 08:13:51 +02:00
}
Infrastructure: tidy up naming of SLOT methods and their usage - Part 2 (#6255) When Qt's slot/signal system is used to invoke a method there is some overhead - so it makes sense to ensure developers can spot all such methods (functions). We have tried to do this with a `slot_` prefix to the methods we create but it has not been applied uniformly. This PR (and one or more to follow) is intended to help with this by more rigorously doing so - the names changed herein should all follow a `slot_`*camelCaseMethodName* style. In addition some methods were detected that are not currently used or which are not actually used via the signal/slot system, these have been commented out or have had the prefix removed and the declaration in the relevant header file moved as appropriate. For reference the changes made are: * `TLuaInterpreter::slotDeleteSender(...)` ==> `TLuaInterpreter::slot_deleteSender(...)` * `TLuaInterpreter::slotPurge()` ==> `TLuaInterpreter::slot_purge()` * `cTelnet::handle_socket_signal_connected()` ==> `cTelnet::slot_socketConnected()` * `cTelnet::handle_socket_signal_disconnected()` ==> `cTelnet::slot_socketDisconnected()` * `cTelnet::handle_socket_signal_hostFound(...)` ==> `cTelnet::slot_socketHostFound(...)` * `cTelnet::handle_socket_signal_readyRead()` ==> `cTelnet::slot_socketReadyToBeRead()` * `cTelnet::handle_socket_signal_sslError(...)` ==> `cTelnet::slot_socketSslError(...)` * `cTelnet::setDownloadProgress(...)` ==> `cTelnet::slot_setDownloadProgress(...)` * `dlgComposer::cancel()` ==> `dlgComposer::slot_cancel()` * `dlgComposer::save()` ==> `dlgComposer::slot_save()` * `dlgConnectionProfiles::slot_item_clicked(...)` ==> `dlgConnectionProfiles::slot_itemClicked(...)` * `dlgConnectionProfiles::slot_save_name()` ==> `dlgConnectionProfiles::slot_saveName()` * `dlgConnectionProfiles::slot_update_login(...)` ==> `dlgConnectionProfiles::slot_updateLogin(...)` * `dlgConnectionProfiles::slot_update_name(...)` ==> `dlgConnectionProfiles::slot_updateName(...)` * `dlgConnectionProfiles::slot_update_pass(...)` ==> `dlgConnectionProfiles::slot_updatePassword(...)` * `dlgConnectionProfiles::slot_update_port(...)` ==> `dlgConnectionProfiles::slot_updatePort(...)` * `dlgConnectionProfiles::slot_update_SSL_TSL_port(...)` ==> `dlgConnectionProfiles::slot_updateSslTslPort(...)` * `dlgConnectionProfiles::slot_update_url(...)` ==> `dlgConnectionProfiles::slot_updateUrl(...)` * `dlgMapLabel::pickBgColor()` ==> `dlgMapLabel::slot_pickBgColor()` * `dlgMapLabel::pickFgColor()` ==> `dlgMapLabel::slot_pickFgColor()` * `dlgMapLabel::pickFile()` ==> `dlgMapLabel::slot_pickFile()` * `dlgMapLabel::pickFont()` ==> `dlgMapLabel::slot_pickFont()` * `dlgMapLabel::save()` ==> `dlgMapLabel::slot_save()` * `dlgMapLabel::updateControls()` ==> `dlgMapLabel::slot_updateControls()` * `dlgMapLabel::updateControlsVisibility()` ==> `dlgMapLabel::slot_updateControlsVisibility()` Commented out as not being used: * `cTelnet::handle_socket_signal_error()` ==> `cTelnet::slot_socketError()` * `dlgConnectionProfiles::slot_update_website(...) ==> `dlgConnectionProfiles::slot_updateWebsite(const QString& url)` I have my doubts about whether the `dlgMapLabel::save()`/`dlgMapLabel::slot_save()` method is needed at all, as it might be that the `QDialog::accept()` slot could have been inserted into the `QObject::connect(...)` call directly instead...? Signed-off-by: Stephen Lyons <slysven@virginmedia.com>
2022-08-27 10:47:01 +02:00
void dlgConnectionProfiles::slot_itemClicked(QListWidgetItem* pItem)
{
if (!pItem) {
qDebug() << "dlgConnectionProfiles::slot_itemClicked() called with null item";
return;
}
// on a fresh install picking a game has to swap the welcome message for
// the connection details, just as creating a new profile does
revealConnectionDetails();
Refactor: remove minute visible name from Connection dialogue icons We have been trying to hide the text associated with the `QListWidgetItems` in the connection dialogue by setting the font size to the minimum of `1` and by setting it's colour to be white. This is not effective when the background is not white - which is likely for a "Dark" desktop environment. The only way to successfully hide the text is, I think, to not have any! However the text was being used programmatically, so the best way to use a `QListWidget` in this mannar is to store the text elsewhere in each item's structure. Fortunately Qt provides for this with the user data functionality which allows multiple data items (based on the `QVariant` class) to be stored within each `QListWidgetItem` using an integer key to denote the type of the data. Ironically the text, icon and other details for each item are ALSO stored in this way - however for non-Qt internal use the lowest integer key that is to be used is `Qt::UserRole` - which I have assigned to the `(const int) dlgConnectionProfiles::csmNameRole` static value. It is quite possible that a redesign of the Connect Profiles dialogue may use this system to store/cache more details about each profile in the future! The only issue with this is the lack of a: `QListWidget::findData(const QVarient&data, int role = Qt::UserRole ...)` method (c.f. `QComboBox::findData(...)`) so I have had to provide a: `(QList<QListWidgetItem*>) findData(const QListWidget&, const QVariant&, const int role = Qt::UserRole) const; method to fill in this gap and to replace the previous `QListWidget::find(...)` that examined each item's text. Also removed local `(QString) profile` from: `(void) dlgConnectionProfiles::slot_item_clicked(QListWidgetItem*)` as it is merely a redundent duplicate of another local `(QString) profile_name`. Renamed local `(QListWidgetItem*) pM` from: `(void) dlgConnectionProfiles::fillout_form()` to: `(QListWidgetItem*) pItem` as that reflects the name used throughout the rest of the class for this type of variable. Changed a `(QLabel*)::setText(tr(""))` call to the more explicit and less stupid `(QLabel*)::clear()`. Similarly changed a `(QWidget*)::setToolTip("")` to a `(QWidget*)::setToolTip(QString())`. Signed-off-by: Stephen Lyons <slysven@virginmedia.com>
2020-07-01 20:59:42 +01:00
const QString profile_name = pItem->data(csmNameRole).toString();
// Prevent rapid duplicate clicks on the same profile
static QString lastProfileClicked;
static QTime lastClickTime;
if (profile_name == lastProfileClicked && lastClickTime.isValid() && lastClickTime.msecsTo(QTime::currentTime()) < 100) {
return;
}
lastProfileClicked = profile_name;
lastClickTime = QTime::currentTime();
slot_togglePasswordVisibility(false);
welcome_message->hide();
tabWidget_connectionInfo->show();
informationArea->show();
profile_name_entry->setText(profile_name);
QString host_url = readProfileData(profile_name, qsl("url"));
if (host_url.isEmpty()) {
// Host to connect to, see below for port
auto it = TGameDetails::findGame(profile_name);
if (it != TGameDetails::scmDefaultGames.end()) {
host_url = (*it).hostUrl;
}
}
host_name_entry->setText(host_url);
QString host_port = readProfileData(profile_name, qsl("port"));
QString val = readProfileData(profile_name, qsl("ssl_tsl"));
2019-01-06 06:29:16 -05:00
if (val.toInt() == Qt::Checked) {
port_ssl_tsl->setChecked(true);
} else {
port_ssl_tsl->setChecked(false);
}
if (host_port.isEmpty()) {
auto it = TGameDetails::findGame(profile_name);
if (it != TGameDetails::scmDefaultGames.end()) {
host_port = QString::number((*it).port);
port_ssl_tsl->setChecked((*it).tlsEnabled);
}
}
2019-01-06 06:29:16 -05:00
port_entry->setText(host_port);
// if we're currently copying a profile, don't blank and re-load the password,
// because there isn't one in storage yet. It'll be copied over into the widget
// by the copy method
if (!mCopyingProfile) {
// Cancel any pending password save from the previous profile to prevent
// cross-profile password corruption when rapidly switching profiles
if (mPasswordSaveTimer) {
mPasswordSaveTimer->stop();
}
mPendingPasswordSaveProfile.clear();
// Block signals when clearing to prevent triggering a save for the wrong profile
{
const QSignalBlocker blocker(character_password_entry);
character_password_entry->setText(QString());
}
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 06:44:38 -04:00
// Schedule password loading asynchronously to avoid event loop issues
auto* timer = new QTimer(this);
timer->setSingleShot(true);
timer->setProperty("profileName", profile_name);
connect(timer, &QTimer::timeout, this, &dlgConnectionProfiles::slot_loadPasswordAsync);
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
timer->start(0ms);
}
val = readProfileData(profile_name, qsl("login"));
login_entry->setText(val);
val = readProfileData(profile_name, qsl("autologin"));
if (val.toInt() == Qt::Checked) {
autologin_checkBox->setChecked(true);
} else {
autologin_checkBox->setChecked(false);
}
Update: remove Midkemia (and add WoTMUD) to list of predefined MUDs (#321) * Update: remove Midkemia (and add WoTMUD) to list of predefined MUDs The I.R.E. MUD Midkemia-online was shutdown on 2016/09/07 and it is not helpful to continue to offer it as a predefined MUD in the connection dialog. This commit removes it from that list and adds a favourite of mine "WoTMUD" to replace it. The logo is a temporary one that I will replace with a similar one that I need to get clearance to use (and release with a GPL2.1+ licence) from the Wheel of Time MUD sysop with whom I am in contact {Vivienne needs to get approval from the creator Flash who is away for a few days...!} The dlgConnectionProfiles.cpp file has quite a few ASCII strings, as appropriate for a file that will in the future be subject to I18n work I took the opportunity to put QStringLiteral(...) wrappers around the QStrings that are used for non-GUI tasks and tr(...) ones that are. I expect it isn't complete but it goes someway towards dealing with THIS particular file - and it should improve very slightly the generation of QStrings that need to be constructed because it means as much of possible of them is done at compile rather than run-time...! In relation to the above there are points in the code where QStrings are compared to "" to see if they do not have any contents - the isEmpty() method is a better way to do the same (it does not involve a string expression) In (void)dlgConnectionProfiles::slot_update_name( const QString ) there are a couple of variables that use a double underscore prefix - that is NOT A Good Idea: such a prefix is RESERVED in both C and C++. For example see: [what-are-the-rules-about-using-an-underscore-in-a-c-identifier](http://stackoverflow.com/questions/228783/what-are-the-rules-about-using-an-underscore-in-a-c-identifier) also a single prefix might be a bit of a problem with libraries... I have renames the offending things there - but I may have used the single underscore prefix myself in the past. *blush* There are some help messages in this class that use an apostrophe in a word contraction - as a matter of readability this is not considered best practice IMHO so I have converted them to the full words. Following experiences in my previous Pull Request I also took a look at the initialisation and use of some member variables; it became clear that: * (QString) dlgConnectionProfiles::mOrigin * (bool) dlgConnectionProfiles::mEditOK were not used and could be simply deleted and that: * (QString) dlgConnectionProfiles::mUnsavedProfileName * (QString) dlgConnectionProfiles::mCurrentProfileEditName did not need to be member variables and could be local to the method that used them, so in (void)dlgConnectionProfiles::slot_save_name(): * (QString) dlgConnectionProfiles::mCurrentProfileEditName becomes local: (QString) currentProfileEditName and in (void) dlgConnectionProfiles::slot_addProfile(): * (QString) dlgConnectionProfiles::mUnsavedProfileName becomes local: (QString) newName also added initialisers for: * (QStringList) dlgConnectionProfiles::mProfileList * (QPushButton *) dlgConnectionProfiles::connect_button * (QLineEditQPushButton *) dlgConnectionProfiles::delete_profile_lineedit * (QPushButton *) dlgConnectionProfiles::delete_button Signed-off-by: Stephen Lyons <slysven@virginmedia.com> * More: two items missed from prior commit In the previous commit I forgot to take out: * a check for Midkemia in the dlgProfilePreferences class constructor that enabled the download map options; * a similar check in (bool)TMap::restore(QString) that asked if the user wanted to download a map if there was not one found. Signed-off-by: Stephen Lyons <slysven@virginmedia.com> * Tweak: Insert MUD supplied Logo/Icon Received an icon as a .jpg that the MUD operators can allow us to licence as GPL2.1+ which I have converted to a .png file. Signed-off-by: Stephen Lyons <slysven@virginmedia.com>
2016-10-15 09:57:17 +01:00
val = readProfileData(profile_name, qsl("autoreconnect"));
2019-01-06 06:29:16 -05:00
if (!val.isEmpty() && val.toInt() == Qt::Checked) {
auto_reconnect->setChecked(true);
} else {
auto_reconnect->setChecked(false);
}
mDiscordApplicationId = readProfileData(profile_name, qsl("discordApplicationId"));
mDiscordInviteURL = readProfileData(profile_name, qsl("discordInviteURL"));
2018-10-05 06:25:57 +02:00
mud_description_textedit->setPlainText(getDescription(profile_name));
Update: remove Midkemia (and add WoTMUD) to list of predefined MUDs (#321) * Update: remove Midkemia (and add WoTMUD) to list of predefined MUDs The I.R.E. MUD Midkemia-online was shutdown on 2016/09/07 and it is not helpful to continue to offer it as a predefined MUD in the connection dialog. This commit removes it from that list and adds a favourite of mine "WoTMUD" to replace it. The logo is a temporary one that I will replace with a similar one that I need to get clearance to use (and release with a GPL2.1+ licence) from the Wheel of Time MUD sysop with whom I am in contact {Vivienne needs to get approval from the creator Flash who is away for a few days...!} The dlgConnectionProfiles.cpp file has quite a few ASCII strings, as appropriate for a file that will in the future be subject to I18n work I took the opportunity to put QStringLiteral(...) wrappers around the QStrings that are used for non-GUI tasks and tr(...) ones that are. I expect it isn't complete but it goes someway towards dealing with THIS particular file - and it should improve very slightly the generation of QStrings that need to be constructed because it means as much of possible of them is done at compile rather than run-time...! In relation to the above there are points in the code where QStrings are compared to "" to see if they do not have any contents - the isEmpty() method is a better way to do the same (it does not involve a string expression) In (void)dlgConnectionProfiles::slot_update_name( const QString ) there are a couple of variables that use a double underscore prefix - that is NOT A Good Idea: such a prefix is RESERVED in both C and C++. For example see: [what-are-the-rules-about-using-an-underscore-in-a-c-identifier](http://stackoverflow.com/questions/228783/what-are-the-rules-about-using-an-underscore-in-a-c-identifier) also a single prefix might be a bit of a problem with libraries... I have renames the offending things there - but I may have used the single underscore prefix myself in the past. *blush* There are some help messages in this class that use an apostrophe in a word contraction - as a matter of readability this is not considered best practice IMHO so I have converted them to the full words. Following experiences in my previous Pull Request I also took a look at the initialisation and use of some member variables; it became clear that: * (QString) dlgConnectionProfiles::mOrigin * (bool) dlgConnectionProfiles::mEditOK were not used and could be simply deleted and that: * (QString) dlgConnectionProfiles::mUnsavedProfileName * (QString) dlgConnectionProfiles::mCurrentProfileEditName did not need to be member variables and could be local to the method that used them, so in (void)dlgConnectionProfiles::slot_save_name(): * (QString) dlgConnectionProfiles::mCurrentProfileEditName becomes local: (QString) currentProfileEditName and in (void) dlgConnectionProfiles::slot_addProfile(): * (QString) dlgConnectionProfiles::mUnsavedProfileName becomes local: (QString) newName also added initialisers for: * (QStringList) dlgConnectionProfiles::mProfileList * (QPushButton *) dlgConnectionProfiles::connect_button * (QLineEditQPushButton *) dlgConnectionProfiles::delete_profile_lineedit * (QPushButton *) dlgConnectionProfiles::delete_button Signed-off-by: Stephen Lyons <slysven@virginmedia.com> * More: two items missed from prior commit In the previous commit I forgot to take out: * a check for Midkemia in the dlgProfilePreferences class constructor that enabled the download map options; * a similar check in (bool)TMap::restore(QString) that asked if the user wanted to download a map if there was not one found. Signed-off-by: Stephen Lyons <slysven@virginmedia.com> * Tweak: Insert MUD supplied Logo/Icon Received an icon as a .jpg that the MUD operators can allow us to licence as GPL2.1+ which I have converted to a .png file. Signed-off-by: Stephen Lyons <slysven@virginmedia.com>
2016-10-15 09:57:17 +01:00
val = readProfileData(profile_name, qsl("website"));
if (val.isEmpty()) {
auto it = TGameDetails::findGame(profile_name);
if (it != TGameDetails::scmDefaultGames.end()) {
val = (*it).websiteInfo;
}
website_entry->setVisible(!val.isEmpty());
} else {
website_entry->show();
}
website_entry->setText(val);
2010-03-15 09:37:16 +01:00
profile_history->clear();
2010-03-15 09:37:16 +01:00
QDir dir(mudlet::getMudletPath(enums::profileXmlFilesPath, profile_name));
dir.setSorting(QDir::Time);
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
// Only offer real profile saves (*.xml) as history entries; leftover QSaveFile
// temporaries from an interrupted save (e.g. "....xml.AbCdEf") must not be loadable
const QStringList entries = dir.entryList(QStringList{qsl("*.xml")}, QDir::Files | QDir::NoDotAndDotDot, QDir::Time);
2010-03-15 09:37:16 +01:00
for (const auto& entry : entries) {
const QRegularExpression rx(qsl("(\\d+)\\-(\\d+)\\-(\\d+)#(\\d+)\\-(\\d+)\\-(\\d+).xml"));
const QRegularExpressionMatch match = rx.match(entry);
if (match.capturedStart() != -1) {
Update: remove Midkemia (and add WoTMUD) to list of predefined MUDs (#321) * Update: remove Midkemia (and add WoTMUD) to list of predefined MUDs The I.R.E. MUD Midkemia-online was shutdown on 2016/09/07 and it is not helpful to continue to offer it as a predefined MUD in the connection dialog. This commit removes it from that list and adds a favourite of mine "WoTMUD" to replace it. The logo is a temporary one that I will replace with a similar one that I need to get clearance to use (and release with a GPL2.1+ licence) from the Wheel of Time MUD sysop with whom I am in contact {Vivienne needs to get approval from the creator Flash who is away for a few days...!} The dlgConnectionProfiles.cpp file has quite a few ASCII strings, as appropriate for a file that will in the future be subject to I18n work I took the opportunity to put QStringLiteral(...) wrappers around the QStrings that are used for non-GUI tasks and tr(...) ones that are. I expect it isn't complete but it goes someway towards dealing with THIS particular file - and it should improve very slightly the generation of QStrings that need to be constructed because it means as much of possible of them is done at compile rather than run-time...! In relation to the above there are points in the code where QStrings are compared to "" to see if they do not have any contents - the isEmpty() method is a better way to do the same (it does not involve a string expression) In (void)dlgConnectionProfiles::slot_update_name( const QString ) there are a couple of variables that use a double underscore prefix - that is NOT A Good Idea: such a prefix is RESERVED in both C and C++. For example see: [what-are-the-rules-about-using-an-underscore-in-a-c-identifier](http://stackoverflow.com/questions/228783/what-are-the-rules-about-using-an-underscore-in-a-c-identifier) also a single prefix might be a bit of a problem with libraries... I have renames the offending things there - but I may have used the single underscore prefix myself in the past. *blush* There are some help messages in this class that use an apostrophe in a word contraction - as a matter of readability this is not considered best practice IMHO so I have converted them to the full words. Following experiences in my previous Pull Request I also took a look at the initialisation and use of some member variables; it became clear that: * (QString) dlgConnectionProfiles::mOrigin * (bool) dlgConnectionProfiles::mEditOK were not used and could be simply deleted and that: * (QString) dlgConnectionProfiles::mUnsavedProfileName * (QString) dlgConnectionProfiles::mCurrentProfileEditName did not need to be member variables and could be local to the method that used them, so in (void)dlgConnectionProfiles::slot_save_name(): * (QString) dlgConnectionProfiles::mCurrentProfileEditName becomes local: (QString) currentProfileEditName and in (void) dlgConnectionProfiles::slot_addProfile(): * (QString) dlgConnectionProfiles::mUnsavedProfileName becomes local: (QString) newName also added initialisers for: * (QStringList) dlgConnectionProfiles::mProfileList * (QPushButton *) dlgConnectionProfiles::connect_button * (QLineEditQPushButton *) dlgConnectionProfiles::delete_profile_lineedit * (QPushButton *) dlgConnectionProfiles::delete_button Signed-off-by: Stephen Lyons <slysven@virginmedia.com> * More: two items missed from prior commit In the previous commit I forgot to take out: * a check for Midkemia in the dlgProfilePreferences class constructor that enabled the download map options; * a similar check in (bool)TMap::restore(QString) that asked if the user wanted to download a map if there was not one found. Signed-off-by: Stephen Lyons <slysven@virginmedia.com> * Tweak: Insert MUD supplied Logo/Icon Received an icon as a .jpg that the MUD operators can allow us to licence as GPL2.1+ which I have converted to a .png file. Signed-off-by: Stephen Lyons <slysven@virginmedia.com>
2016-10-15 09:57:17 +01:00
QString day;
const QString month = match.captured(2);
Update: remove Midkemia (and add WoTMUD) to list of predefined MUDs (#321) * Update: remove Midkemia (and add WoTMUD) to list of predefined MUDs The I.R.E. MUD Midkemia-online was shutdown on 2016/09/07 and it is not helpful to continue to offer it as a predefined MUD in the connection dialog. This commit removes it from that list and adds a favourite of mine "WoTMUD" to replace it. The logo is a temporary one that I will replace with a similar one that I need to get clearance to use (and release with a GPL2.1+ licence) from the Wheel of Time MUD sysop with whom I am in contact {Vivienne needs to get approval from the creator Flash who is away for a few days...!} The dlgConnectionProfiles.cpp file has quite a few ASCII strings, as appropriate for a file that will in the future be subject to I18n work I took the opportunity to put QStringLiteral(...) wrappers around the QStrings that are used for non-GUI tasks and tr(...) ones that are. I expect it isn't complete but it goes someway towards dealing with THIS particular file - and it should improve very slightly the generation of QStrings that need to be constructed because it means as much of possible of them is done at compile rather than run-time...! In relation to the above there are points in the code where QStrings are compared to "" to see if they do not have any contents - the isEmpty() method is a better way to do the same (it does not involve a string expression) In (void)dlgConnectionProfiles::slot_update_name( const QString ) there are a couple of variables that use a double underscore prefix - that is NOT A Good Idea: such a prefix is RESERVED in both C and C++. For example see: [what-are-the-rules-about-using-an-underscore-in-a-c-identifier](http://stackoverflow.com/questions/228783/what-are-the-rules-about-using-an-underscore-in-a-c-identifier) also a single prefix might be a bit of a problem with libraries... I have renames the offending things there - but I may have used the single underscore prefix myself in the past. *blush* There are some help messages in this class that use an apostrophe in a word contraction - as a matter of readability this is not considered best practice IMHO so I have converted them to the full words. Following experiences in my previous Pull Request I also took a look at the initialisation and use of some member variables; it became clear that: * (QString) dlgConnectionProfiles::mOrigin * (bool) dlgConnectionProfiles::mEditOK were not used and could be simply deleted and that: * (QString) dlgConnectionProfiles::mUnsavedProfileName * (QString) dlgConnectionProfiles::mCurrentProfileEditName did not need to be member variables and could be local to the method that used them, so in (void)dlgConnectionProfiles::slot_save_name(): * (QString) dlgConnectionProfiles::mCurrentProfileEditName becomes local: (QString) currentProfileEditName and in (void) dlgConnectionProfiles::slot_addProfile(): * (QString) dlgConnectionProfiles::mUnsavedProfileName becomes local: (QString) newName also added initialisers for: * (QStringList) dlgConnectionProfiles::mProfileList * (QPushButton *) dlgConnectionProfiles::connect_button * (QLineEditQPushButton *) dlgConnectionProfiles::delete_profile_lineedit * (QPushButton *) dlgConnectionProfiles::delete_button Signed-off-by: Stephen Lyons <slysven@virginmedia.com> * More: two items missed from prior commit In the previous commit I forgot to take out: * a check for Midkemia in the dlgProfilePreferences class constructor that enabled the download map options; * a similar check in (bool)TMap::restore(QString) that asked if the user wanted to download a map if there was not one found. Signed-off-by: Stephen Lyons <slysven@virginmedia.com> * Tweak: Insert MUD supplied Logo/Icon Received an icon as a .jpg that the MUD operators can allow us to licence as GPL2.1+ which I have converted to a .png file. Signed-off-by: Stephen Lyons <slysven@virginmedia.com>
2016-10-15 09:57:17 +01:00
QString year;
const QString hour = match.captured(4);
const QString minute = match.captured(5);
const QString second = match.captured(6);
if (match.captured(1).toInt() > 31 && match.captured(3).toInt() >= 1 && match.captured(3).toInt() <= 31) {
Update: remove Midkemia (and add WoTMUD) to list of predefined MUDs (#321) * Update: remove Midkemia (and add WoTMUD) to list of predefined MUDs The I.R.E. MUD Midkemia-online was shutdown on 2016/09/07 and it is not helpful to continue to offer it as a predefined MUD in the connection dialog. This commit removes it from that list and adds a favourite of mine "WoTMUD" to replace it. The logo is a temporary one that I will replace with a similar one that I need to get clearance to use (and release with a GPL2.1+ licence) from the Wheel of Time MUD sysop with whom I am in contact {Vivienne needs to get approval from the creator Flash who is away for a few days...!} The dlgConnectionProfiles.cpp file has quite a few ASCII strings, as appropriate for a file that will in the future be subject to I18n work I took the opportunity to put QStringLiteral(...) wrappers around the QStrings that are used for non-GUI tasks and tr(...) ones that are. I expect it isn't complete but it goes someway towards dealing with THIS particular file - and it should improve very slightly the generation of QStrings that need to be constructed because it means as much of possible of them is done at compile rather than run-time...! In relation to the above there are points in the code where QStrings are compared to "" to see if they do not have any contents - the isEmpty() method is a better way to do the same (it does not involve a string expression) In (void)dlgConnectionProfiles::slot_update_name( const QString ) there are a couple of variables that use a double underscore prefix - that is NOT A Good Idea: such a prefix is RESERVED in both C and C++. For example see: [what-are-the-rules-about-using-an-underscore-in-a-c-identifier](http://stackoverflow.com/questions/228783/what-are-the-rules-about-using-an-underscore-in-a-c-identifier) also a single prefix might be a bit of a problem with libraries... I have renames the offending things there - but I may have used the single underscore prefix myself in the past. *blush* There are some help messages in this class that use an apostrophe in a word contraction - as a matter of readability this is not considered best practice IMHO so I have converted them to the full words. Following experiences in my previous Pull Request I also took a look at the initialisation and use of some member variables; it became clear that: * (QString) dlgConnectionProfiles::mOrigin * (bool) dlgConnectionProfiles::mEditOK were not used and could be simply deleted and that: * (QString) dlgConnectionProfiles::mUnsavedProfileName * (QString) dlgConnectionProfiles::mCurrentProfileEditName did not need to be member variables and could be local to the method that used them, so in (void)dlgConnectionProfiles::slot_save_name(): * (QString) dlgConnectionProfiles::mCurrentProfileEditName becomes local: (QString) currentProfileEditName and in (void) dlgConnectionProfiles::slot_addProfile(): * (QString) dlgConnectionProfiles::mUnsavedProfileName becomes local: (QString) newName also added initialisers for: * (QStringList) dlgConnectionProfiles::mProfileList * (QPushButton *) dlgConnectionProfiles::connect_button * (QLineEditQPushButton *) dlgConnectionProfiles::delete_profile_lineedit * (QPushButton *) dlgConnectionProfiles::delete_button Signed-off-by: Stephen Lyons <slysven@virginmedia.com> * More: two items missed from prior commit In the previous commit I forgot to take out: * a check for Midkemia in the dlgProfilePreferences class constructor that enabled the download map options; * a similar check in (bool)TMap::restore(QString) that asked if the user wanted to download a map if there was not one found. Signed-off-by: Stephen Lyons <slysven@virginmedia.com> * Tweak: Insert MUD supplied Logo/Icon Received an icon as a .jpg that the MUD operators can allow us to licence as GPL2.1+ which I have converted to a .png file. Signed-off-by: Stephen Lyons <slysven@virginmedia.com>
2016-10-15 09:57:17 +01:00
// I have been experimenting with code that puts the year first
// which is actually quite useful - this accommodates such cases
// as well... - SlySven
year = match.captured(1);
day = match.captured(3);
} else {
day = match.captured(1);
year = match.captured(3);
Update: remove Midkemia (and add WoTMUD) to list of predefined MUDs (#321) * Update: remove Midkemia (and add WoTMUD) to list of predefined MUDs The I.R.E. MUD Midkemia-online was shutdown on 2016/09/07 and it is not helpful to continue to offer it as a predefined MUD in the connection dialog. This commit removes it from that list and adds a favourite of mine "WoTMUD" to replace it. The logo is a temporary one that I will replace with a similar one that I need to get clearance to use (and release with a GPL2.1+ licence) from the Wheel of Time MUD sysop with whom I am in contact {Vivienne needs to get approval from the creator Flash who is away for a few days...!} The dlgConnectionProfiles.cpp file has quite a few ASCII strings, as appropriate for a file that will in the future be subject to I18n work I took the opportunity to put QStringLiteral(...) wrappers around the QStrings that are used for non-GUI tasks and tr(...) ones that are. I expect it isn't complete but it goes someway towards dealing with THIS particular file - and it should improve very slightly the generation of QStrings that need to be constructed because it means as much of possible of them is done at compile rather than run-time...! In relation to the above there are points in the code where QStrings are compared to "" to see if they do not have any contents - the isEmpty() method is a better way to do the same (it does not involve a string expression) In (void)dlgConnectionProfiles::slot_update_name( const QString ) there are a couple of variables that use a double underscore prefix - that is NOT A Good Idea: such a prefix is RESERVED in both C and C++. For example see: [what-are-the-rules-about-using-an-underscore-in-a-c-identifier](http://stackoverflow.com/questions/228783/what-are-the-rules-about-using-an-underscore-in-a-c-identifier) also a single prefix might be a bit of a problem with libraries... I have renames the offending things there - but I may have used the single underscore prefix myself in the past. *blush* There are some help messages in this class that use an apostrophe in a word contraction - as a matter of readability this is not considered best practice IMHO so I have converted them to the full words. Following experiences in my previous Pull Request I also took a look at the initialisation and use of some member variables; it became clear that: * (QString) dlgConnectionProfiles::mOrigin * (bool) dlgConnectionProfiles::mEditOK were not used and could be simply deleted and that: * (QString) dlgConnectionProfiles::mUnsavedProfileName * (QString) dlgConnectionProfiles::mCurrentProfileEditName did not need to be member variables and could be local to the method that used them, so in (void)dlgConnectionProfiles::slot_save_name(): * (QString) dlgConnectionProfiles::mCurrentProfileEditName becomes local: (QString) currentProfileEditName and in (void) dlgConnectionProfiles::slot_addProfile(): * (QString) dlgConnectionProfiles::mUnsavedProfileName becomes local: (QString) newName also added initialisers for: * (QStringList) dlgConnectionProfiles::mProfileList * (QPushButton *) dlgConnectionProfiles::connect_button * (QLineEditQPushButton *) dlgConnectionProfiles::delete_profile_lineedit * (QPushButton *) dlgConnectionProfiles::delete_button Signed-off-by: Stephen Lyons <slysven@virginmedia.com> * More: two items missed from prior commit In the previous commit I forgot to take out: * a check for Midkemia in the dlgProfilePreferences class constructor that enabled the download map options; * a similar check in (bool)TMap::restore(QString) that asked if the user wanted to download a map if there was not one found. Signed-off-by: Stephen Lyons <slysven@virginmedia.com> * Tweak: Insert MUD supplied Logo/Icon Received an icon as a .jpg that the MUD operators can allow us to licence as GPL2.1+ which I have converted to a .png file. Signed-off-by: Stephen Lyons <slysven@virginmedia.com>
2016-10-15 09:57:17 +01:00
}
QDateTime datetime;
datetime.setTime(QTime(hour.toInt(), minute.toInt(), second.toInt()));
datetime.setDate(QDate(year.toInt(), month.toInt(), day.toInt()));
profile_history->addItem(mudlet::self()->getUserLocale().toString(datetime, mDateTimeFormat), QVariant(entry));
2018-07-11 20:52:02 +02:00
} else if (entry == QLatin1String("autosave.xml")) {
const QFileInfo fileInfo(dir, entry);
2018-07-11 20:52:02 +02:00
auto lastModified = fileInfo.lastModified();
profile_history->addItem(
QIcon::fromTheme(qsl("document-save"), QIcon(qsl(":/icons/document-save.png"))), mudlet::self()->getUserLocale().toString(lastModified, mDateTimeFormat), QVariant(entry));
} else if (entry.endsWith(QLatin1String(".xml"), Qt::CaseInsensitive)) {
2018-07-11 20:52:02 +02:00
profile_history->addItem(entry, QVariant(entry)); // if it has a custom name, use it as it is
}
}
2010-03-15 09:37:16 +01:00
profile_history->setEnabled(static_cast<bool>(profile_history->count()));
2010-03-15 09:37:16 +01:00
const QString profileLoadedMessage = tr("This profile is currently loaded - close it before changing the connection parameters.");
Clean up console access code (#4186) * Add a new Host::findConsole function * Sketch: TLuaInterpreter.cpp modification * Fix macros to eval their argument once * More adaptions * TLuaInterpreter: use WINDOW_NAME macro. Some cases not touched because difficult. * macro-ify a lot of functions * Remove no-longer-needed accessors * Clean up font and console handling * unify setFontSize and setMiniConsoleFontSize the latter is now an alias to the former * TConsole methods don't need "MiniConsole" in their name that's redundant * teach Host::updateConsolesFont() to refresh the main console no need to expose these details * Fix font setting * Removed a bunch more unused methods from mudlet.cpp * replace HostManager::getHostList It was only used to * create a list to test membership against just call getHost and check for null * create a list of host names, each of which is then looked up which is unnecessary work * Remove the "QMap<Host*, T*>" anti-pattern It makes much more sense to add a T* member to the Host class. * Allow window names beyond the Lua stack simply treat a missing parameter (or nil) as empty string * Use a nested class for iteration * Fix host iterator * move getLines() to Host * remove mudlet::mTabMap Redundant, the host map and Host::mpConsole works. * more moves to host * Move host-close code to the host * Move even more stuff from mudlet:: to THost:: Also drop mudlet::mpProfilePreferencesDlgMap in favor of a pointer in THost. Sorry that this got mashed in but I was halfway through this before I noticed and didn't want to do the work twice. * These things are pointers, thus should be named as such as annoying as that is … * Vanquish another two pointer-to-host maps * Move host-specific layout updates to THost This patch also removes layering violations in TToolbar and TDockWidget. * mudlet.h include directives cleanup * appease CodeFactor * variable rename * Check for valid dialog * minor cleanup * Revert mistaken checkin of test code from d918164c77382332ad1cd9bd31b2d1ea173267fd * rename function in error messages * Crash fix * renamed args for clarity * Braces please * Bugfix: "echo" didn't talk to labels * Found two wrongly-named exceptions * Clean up merge differences * Merge error * Fix potential NULL deref * Re-add the option to have a prefs dialog without a host * Don't delete the prefs dialog on close * Fix "Don't delete the prefs dialog on close" another way. Apparently Qt correctly handles this when using a QScopedPointer but not using a QSharedPointer. That is, even though the shared pointer still references the dialog, it's killed off without clearing the pointer. Since keeping the dialog around forever isn't a good idea, replace with a QPointer which *is* cleared. Co-authored-by: Vadim Peretokin <vperetokin@gmail.com>
2020-11-04 18:10:21 +01:00
if (mudlet::self()->getHostManager().getHost(profile_name)) {
remove_profile_button->setEnabled(false);
remove_profile_button->setToolTip(utils::richText(tr("A profile that is in use cannot be removed")));
connect_button->setEnabled(false);
offline_button->setEnabled(false);
profile_name_entry->setReadOnly(true);
host_name_entry->setReadOnly(true);
port_entry->setReadOnly(true);
profile_name_entry->setFocusPolicy(Qt::NoFocus);
host_name_entry->setFocusPolicy(Qt::NoFocus);
port_entry->setFocusPolicy(Qt::NoFocus);
profile_name_entry->setPalette(mReadOnlyPalette);
host_name_entry->setPalette(mReadOnlyPalette);
port_entry->setPalette(mReadOnlyPalette);
notificationArea->show();
notificationAreaIconLabelWarning->hide();
notificationAreaIconLabelError->hide();
notificationAreaIconLabelInformation->show();
notificationAreaMessageBox->show();
notificationAreaMessageBox->setText(profileLoadedMessage);
} else {
profile_name_entry->setReadOnly(false);
host_name_entry->setReadOnly(false);
port_entry->setReadOnly(false);
profile_name_entry->setFocusPolicy(Qt::StrongFocus);
host_name_entry->setFocusPolicy(Qt::StrongFocus);
port_entry->setFocusPolicy(Qt::StrongFocus);
profile_name_entry->setPalette(mRegularPalette);
host_name_entry->setPalette(mRegularPalette);
port_entry->setPalette(mRegularPalette);
if (notificationAreaMessageBox->text() == profileLoadedMessage) {
2021-02-03 19:59:35 +00:00
clearNotificationArea();
2010-03-15 09:37:16 +01:00
}
remove_profile_button->setEnabled(true);
remove_profile_button->setToolTip(QString());
2009-01-24 02:50:22 +01:00
}
}
// (re-)creates the dialogs profile list
void dlgConnectionProfiles::fillout_form()
{
listWidget_profiles->clear();
profile_name_entry->clear();
host_name_entry->clear();
port_entry->clear();
2010-03-15 09:37:16 +01:00
mProfileList = QDir(mudlet::getMudletPath(enums::profilesPath)).entryList(QDir::Dirs | QDir::NoDotAndDotDot, QDir::Name);
// mProfileList gains non-disk entries (e.g. the QT_DEBUG-only self-test
// profile) further down, so capture whether the user has any saved
// profiles while it still only holds the on-disk ones:
const bool noSavedProfiles = mProfileList.isEmpty();
2010-03-15 09:37:16 +01:00
if (noSavedProfiles) {
// remember the height so revealConnectionDetails() can undo the
// shrink below, but not when the welcome message is already up as the
// dialog is then already shrunken
if (!mDialogHeightBeforeShrink || welcome_message->isHidden()) {
mDialogHeightBeforeShrink = height();
}
// hide before show: with both visible for a moment the layout grows
// the dialog to fit them together and it never shrinks back
tabWidget_connectionInfo->hide();
informationArea->hide();
welcome_message->show();
} else {
2009-01-24 02:50:22 +01:00
welcome_message->hide();
2010-03-15 09:37:16 +01:00
tabWidget_connectionInfo->show();
informationArea->show();
2009-01-24 02:50:22 +01:00
}
2010-03-15 09:37:16 +01:00
listWidget_profiles->setIconSize(QSize(120, 30));
QString description;
Refactor: remove minute visible name from Connection dialogue icons We have been trying to hide the text associated with the `QListWidgetItems` in the connection dialogue by setting the font size to the minimum of `1` and by setting it's colour to be white. This is not effective when the background is not white - which is likely for a "Dark" desktop environment. The only way to successfully hide the text is, I think, to not have any! However the text was being used programmatically, so the best way to use a `QListWidget` in this mannar is to store the text elsewhere in each item's structure. Fortunately Qt provides for this with the user data functionality which allows multiple data items (based on the `QVariant` class) to be stored within each `QListWidgetItem` using an integer key to denote the type of the data. Ironically the text, icon and other details for each item are ALSO stored in this way - however for non-Qt internal use the lowest integer key that is to be used is `Qt::UserRole` - which I have assigned to the `(const int) dlgConnectionProfiles::csmNameRole` static value. It is quite possible that a redesign of the Connect Profiles dialogue may use this system to store/cache more details about each profile in the future! The only issue with this is the lack of a: `QListWidget::findData(const QVarient&data, int role = Qt::UserRole ...)` method (c.f. `QComboBox::findData(...)`) so I have had to provide a: `(QList<QListWidgetItem*>) findData(const QListWidget&, const QVariant&, const int role = Qt::UserRole) const; method to fill in this gap and to replace the previous `QListWidget::find(...)` that examined each item's text. Also removed local `(QString) profile` from: `(void) dlgConnectionProfiles::slot_item_clicked(QListWidgetItem*)` as it is merely a redundent duplicate of another local `(QString) profile_name`. Renamed local `(QListWidgetItem*) pM` from: `(void) dlgConnectionProfiles::fillout_form()` to: `(QListWidgetItem*) pItem` as that reflects the name used throughout the rest of the class for this type of variable. Changed a `(QLabel*)::setText(tr(""))` call to the more explicit and less stupid `(QLabel*)::clear()`. Similarly changed a `(QWidget*)::setToolTip("")` to a `(QWidget*)::setToolTip(QString())`. Signed-off-by: Stephen Lyons <slysven@virginmedia.com>
2020-07-01 20:59:42 +01:00
QListWidgetItem* pItem;
2011-06-16 09:49:34 +02:00
const QStringList& onlyShownPredefinedProfiles{mudlet::self()->mOnlyShownPredefinedProfiles};
const bool showOnlyMyProfiles = showingOnlyMyProfiles();
const QString selfTestProfile = qsl("Mudlet self-test");
const auto deletedDefaultMuds = mudlet::self()->mpSettings->value(qsl("deletedDefaultMuds"), QStringList()).toStringList();
if (onlyShownPredefinedProfiles.isEmpty()) {
const auto defaultGames = TGameDetails::keys();
// "My games" only lists games with profile data on disk; "All games"
// must keep offering every pre-installed game, even ones whose
// profile was deleted (recorded in deletedDefaultMuds). The self-test
// entry is the exception: it is a testing aid rather than a game, and
// is offered even without profile data on disk, so dismissing it has
// to keep it out of both tabs
for (auto& game : defaultGames) {
if (game == selfTestProfile && deletedDefaultMuds.contains(game)) {
continue;
}
if (showOnlyMyProfiles && !mProfileList.contains(game, Qt::CaseInsensitive)) {
continue;
}
pItem = new QListWidgetItem();
auto details = TGameDetails::findGame(game);
setupMudProfile(pItem, game, (*details).description, (*details).icon);
}
#if defined(QT_DEBUG)
if (!deletedDefaultMuds.contains(selfTestProfile) && !mProfileList.contains(selfTestProfile)) {
mProfileList.append(selfTestProfile);
// "All games" already listed it from TGameDetails above, only
// "My games" is still missing an entry:
if (findData(*listWidget_profiles, selfTestProfile, csmNameRole).isEmpty()) {
pItem = new QListWidgetItem();
// Can't use setupMudProfile(...) here as we do not set the icon in the same way:
setItemName(pItem, selfTestProfile);
listWidget_profiles->addItem(pItem);
description = getDescription(qsl("mudlet.org"));
if (!description.isEmpty()) {
pItem->setToolTip(utils::richText(description));
}
}
}
#endif
} else {
for (const QString& onlyShownPredefinedProfile : onlyShownPredefinedProfiles) {
pItem = new QListWidgetItem();
auto details = TGameDetails::findGame(onlyShownPredefinedProfile);
setupMudProfile(pItem, onlyShownPredefinedProfile, (*details).description, (*details).icon);
}
}
Refactor: remove minute visible name from Connection dialogue icons We have been trying to hide the text associated with the `QListWidgetItems` in the connection dialogue by setting the font size to the minimum of `1` and by setting it's colour to be white. This is not effective when the background is not white - which is likely for a "Dark" desktop environment. The only way to successfully hide the text is, I think, to not have any! However the text was being used programmatically, so the best way to use a `QListWidget` in this mannar is to store the text elsewhere in each item's structure. Fortunately Qt provides for this with the user data functionality which allows multiple data items (based on the `QVariant` class) to be stored within each `QListWidgetItem` using an integer key to denote the type of the data. Ironically the text, icon and other details for each item are ALSO stored in this way - however for non-Qt internal use the lowest integer key that is to be used is `Qt::UserRole` - which I have assigned to the `(const int) dlgConnectionProfiles::csmNameRole` static value. It is quite possible that a redesign of the Connect Profiles dialogue may use this system to store/cache more details about each profile in the future! The only issue with this is the lack of a: `QListWidget::findData(const QVarient&data, int role = Qt::UserRole ...)` method (c.f. `QComboBox::findData(...)`) so I have had to provide a: `(QList<QListWidgetItem*>) findData(const QListWidget&, const QVariant&, const int role = Qt::UserRole) const; method to fill in this gap and to replace the previous `QListWidget::find(...)` that examined each item's text. Also removed local `(QString) profile` from: `(void) dlgConnectionProfiles::slot_item_clicked(QListWidgetItem*)` as it is merely a redundent duplicate of another local `(QString) profile_name`. Renamed local `(QListWidgetItem*) pM` from: `(void) dlgConnectionProfiles::fillout_form()` to: `(QListWidgetItem*) pItem` as that reflects the name used throughout the rest of the class for this type of variable. Changed a `(QLabel*)::setText(tr(""))` call to the more explicit and less stupid `(QLabel*)::clear()`. Similarly changed a `(QWidget*)::setToolTip("")` to a `(QWidget*)::setToolTip(QString())`. Signed-off-by: Stephen Lyons <slysven@virginmedia.com>
2020-07-01 20:59:42 +01:00
setProfileIcon();
Improve first-time user experience (#1016) * Fixed the welcome message to show again It didn't before because default_host profile directory was getting created on the disk before the check. * Made it so a random profile gets selected for a first-time launch * Modernized mudlet::startAutoLogin function * Renamed poorly-named method to open connections dialog It also was confusing with Host::connectToServer doing the actual connecting * Made the connection dialog open if no profiles are on autoload * Removed superseded status bar notification The connection dialog now opens automatically. * Improved first-launch Mudlet size and positioning Mudlet is now centered and has a decent size for the most common resolutions * Fix connection dialog to get a decent size by default * Updated the welcome text It was pretty ancient and referenced outdated button names. Also cut down on the wording so a new user isn't hit with a wall of text. * Moved icon over to left so the screen isn't so busy * Undid hardcoded fonts and sizes in connection dialog User-selected desktop fonts and sizes should be used instead. * Fixed connection dialog to actually select the last played profile There was a bug where it would ignore predefined profiles in the calculation. Also use the last modified, not read date - a lot of things like virus scanners could read the directory whereas far fewer things will write to it. * Moved 'loaded profile' warning to be below profiles This prevents the whole thing from jumping around. * Added a margin to the right for aesthetic reasons * Revised to use getDescription as a method * Got tooltips to show on profile icons
2017-06-04 08:13:51 +02:00
QDateTime test_date;
QString toselectProfileName;
int toselectRow = -1;
int test_profile_row = -1;
int predefined_profile_row = -1;
2019-08-15 18:43:39 +02:00
bool firstMudletLaunch = true;
Improve first-time user experience (#1016) * Fixed the welcome message to show again It didn't before because default_host profile directory was getting created on the disk before the check. * Made it so a random profile gets selected for a first-time launch * Modernized mudlet::startAutoLogin function * Renamed poorly-named method to open connections dialog It also was confusing with Host::connectToServer doing the actual connecting * Made the connection dialog open if no profiles are on autoload * Removed superseded status bar notification The connection dialog now opens automatically. * Improved first-launch Mudlet size and positioning Mudlet is now centered and has a decent size for the most common resolutions * Fix connection dialog to get a decent size by default * Updated the welcome text It was pretty ancient and referenced outdated button names. Also cut down on the wording so a new user isn't hit with a wall of text. * Moved icon over to left so the screen isn't so busy * Undid hardcoded fonts and sizes in connection dialog User-selected desktop fonts and sizes should be used instead. * Fixed connection dialog to actually select the last played profile There was a bug where it would ignore predefined profiles in the calculation. Also use the last modified, not read date - a lot of things like virus scanners could read the directory whereas far fewer things will write to it. * Moved 'loaded profile' warning to be below profiles This prevents the whole thing from jumping around. * Added a margin to the right for aesthetic reasons * Revised to use getDescription as a method * Got tooltips to show on profile icons
2017-06-04 08:13:51 +02:00
for (int i = 0; i < listWidget_profiles->count(); i++) {
const auto profile = listWidget_profiles->item(i);
const auto profileName = profile->data(csmNameRole).toString();
if (profileName == qsl("Mudlet self-test")) {
test_profile_row = i;
}
const auto fileinfo = QFileInfo(mudlet::getMudletPath(enums::profileXmlFilesPath, profileName));
2019-08-15 18:43:39 +02:00
if (fileinfo.exists()) {
firstMudletLaunch = false;
const QDateTime profile_lastRead = fileinfo.lastModified();
2019-08-15 18:43:39 +02:00
// Since Qt 5.x null QTimes and QDateTimes are invalid - and might not
// work as expected - so test for validity of the test_date value as well
if ((!test_date.isValid()) || profile_lastRead > test_date) {
test_date = profile_lastRead;
toselectProfileName = profileName;
toselectRow = i;
}
Improve first-time user experience (#1016) * Fixed the welcome message to show again It didn't before because default_host profile directory was getting created on the disk before the check. * Made it so a random profile gets selected for a first-time launch * Modernized mudlet::startAutoLogin function * Renamed poorly-named method to open connections dialog It also was confusing with Host::connectToServer doing the actual connecting * Made the connection dialog open if no profiles are on autoload * Removed superseded status bar notification The connection dialog now opens automatically. * Improved first-launch Mudlet size and positioning Mudlet is now centered and has a decent size for the most common resolutions * Fix connection dialog to get a decent size by default * Updated the welcome text It was pretty ancient and referenced outdated button names. Also cut down on the wording so a new user isn't hit with a wall of text. * Moved icon over to left so the screen isn't so busy * Undid hardcoded fonts and sizes in connection dialog User-selected desktop fonts and sizes should be used instead. * Fixed connection dialog to actually select the last played profile There was a bug where it would ignore predefined profiles in the calculation. Also use the last modified, not read date - a lot of things like virus scanners could read the directory whereas far fewer things will write to it. * Moved 'loaded profile' warning to be below profiles This prevents the whole thing from jumping around. * Added a margin to the right for aesthetic reasons * Revised to use getDescription as a method * Got tooltips to show on profile icons
2017-06-04 08:13:51 +02:00
}
if (!onlyShownPredefinedProfiles.isEmpty() && profileName == onlyShownPredefinedProfiles.first()) {
predefined_profile_row = i;
}
Improve first-time user experience (#1016) * Fixed the welcome message to show again It didn't before because default_host profile directory was getting created on the disk before the check. * Made it so a random profile gets selected for a first-time launch * Modernized mudlet::startAutoLogin function * Renamed poorly-named method to open connections dialog It also was confusing with Host::connectToServer doing the actual connecting * Made the connection dialog open if no profiles are on autoload * Removed superseded status bar notification The connection dialog now opens automatically. * Improved first-launch Mudlet size and positioning Mudlet is now centered and has a decent size for the most common resolutions * Fix connection dialog to get a decent size by default * Updated the welcome text It was pretty ancient and referenced outdated button names. Also cut down on the wording so a new user isn't hit with a wall of text. * Moved icon over to left so the screen isn't so busy * Undid hardcoded fonts and sizes in connection dialog User-selected desktop fonts and sizes should be used instead. * Fixed connection dialog to actually select the last played profile There was a bug where it would ignore predefined profiles in the calculation. Also use the last modified, not read date - a lot of things like virus scanners could read the directory whereas far fewer things will write to it. * Moved 'loaded profile' warning to be below profiles This prevents the whole thing from jumping around. * Added a margin to the right for aesthetic reasons * Revised to use getDescription as a method * Got tooltips to show on profile icons
2017-06-04 08:13:51 +02:00
}
2019-08-15 18:43:39 +02:00
if (firstMudletLaunch) {
if (onlyShownPredefinedProfiles.isEmpty()) {
// Select the tutorial profile on first launch
for (int i = 0; i < listWidget_profiles->count(); i++) {
if (listWidget_profiles->item(i)->data(csmNameRole).toString() == qsl("Mudlet Tutorial")) {
toselectRow = i;
break;
}
}
if (listWidget_profiles->count() == 1 && test_profile_row != 0) {
// The "My games" tab can show a single profile that has not been
// saved to its XML yet, so select it to fill in its details
// instead of leaving the form blank with a game highlighted
toselectRow = 0;
}
} else if (predefined_profile_row >= 0) {
// If the user is starting one of a MUD's "dedicated" Mudlet versions then
// select the first of THAT/THOSE predefined one(s) on first launch:
toselectRow = predefined_profile_row;
}
2009-01-24 02:50:22 +01:00
}
if (toselectRow != -1) {
// this automatic selection must not be taken for the user picking a
// game, which would dismiss the welcome message shown above
mProgrammaticProfileSelection = true;
listWidget_profiles->setCurrentRow(toselectRow);
mProgrammaticProfileSelection = false;
}
2018-10-05 06:25:57 +02:00
// Dedicated single-game builds go straight to their game's profile instead
// of the Mudlet tutorial invitation:
if (firstMudletLaunch && noSavedProfiles && !mTutorialDismissed && onlyShownPredefinedProfiles.isEmpty()) {
// Hide the profile list and show only the tutorial-focused welcome
widget_topLeft->hide();
welcome_message->show();
tabWidget_connectionInfo->hide();
informationArea->hide();
connect_button->hide();
offline_button->hide();
mpSkipToGamesButton->show();
adjustSize();
}
2009-01-24 02:50:22 +01:00
}
Refactor: remove minute visible name from Connection dialogue icons We have been trying to hide the text associated with the `QListWidgetItems` in the connection dialogue by setting the font size to the minimum of `1` and by setting it's colour to be white. This is not effective when the background is not white - which is likely for a "Dark" desktop environment. The only way to successfully hide the text is, I think, to not have any! However the text was being used programmatically, so the best way to use a `QListWidget` in this mannar is to store the text elsewhere in each item's structure. Fortunately Qt provides for this with the user data functionality which allows multiple data items (based on the `QVariant` class) to be stored within each `QListWidgetItem` using an integer key to denote the type of the data. Ironically the text, icon and other details for each item are ALSO stored in this way - however for non-Qt internal use the lowest integer key that is to be used is `Qt::UserRole` - which I have assigned to the `(const int) dlgConnectionProfiles::csmNameRole` static value. It is quite possible that a redesign of the Connect Profiles dialogue may use this system to store/cache more details about each profile in the future! The only issue with this is the lack of a: `QListWidget::findData(const QVarient&data, int role = Qt::UserRole ...)` method (c.f. `QComboBox::findData(...)`) so I have had to provide a: `(QList<QListWidgetItem*>) findData(const QListWidget&, const QVariant&, const int role = Qt::UserRole) const; method to fill in this gap and to replace the previous `QListWidget::find(...)` that examined each item's text. Also removed local `(QString) profile` from: `(void) dlgConnectionProfiles::slot_item_clicked(QListWidgetItem*)` as it is merely a redundent duplicate of another local `(QString) profile_name`. Renamed local `(QListWidgetItem*) pM` from: `(void) dlgConnectionProfiles::fillout_form()` to: `(QListWidgetItem*) pItem` as that reflects the name used throughout the rest of the class for this type of variable. Changed a `(QLabel*)::setText(tr(""))` call to the more explicit and less stupid `(QLabel*)::clear()`. Similarly changed a `(QWidget*)::setToolTip("")` to a `(QWidget*)::setToolTip(QString())`. Signed-off-by: Stephen Lyons <slysven@virginmedia.com>
2020-07-01 20:59:42 +01:00
void dlgConnectionProfiles::setProfileIcon() const
{
const QStringList defaultGames = TGameDetails::keys();
for (const QString& profileName : mProfileList) {
if (profileName.isEmpty()) {
continue;
}
if (hasCustomIcon(profileName)) {
Refactor: remove minute visible name from Connection dialogue icons We have been trying to hide the text associated with the `QListWidgetItems` in the connection dialogue by setting the font size to the minimum of `1` and by setting it's colour to be white. This is not effective when the background is not white - which is likely for a "Dark" desktop environment. The only way to successfully hide the text is, I think, to not have any! However the text was being used programmatically, so the best way to use a `QListWidget` in this mannar is to store the text elsewhere in each item's structure. Fortunately Qt provides for this with the user data functionality which allows multiple data items (based on the `QVariant` class) to be stored within each `QListWidgetItem` using an integer key to denote the type of the data. Ironically the text, icon and other details for each item are ALSO stored in this way - however for non-Qt internal use the lowest integer key that is to be used is `Qt::UserRole` - which I have assigned to the `(const int) dlgConnectionProfiles::csmNameRole` static value. It is quite possible that a redesign of the Connect Profiles dialogue may use this system to store/cache more details about each profile in the future! The only issue with this is the lack of a: `QListWidget::findData(const QVarient&data, int role = Qt::UserRole ...)` method (c.f. `QComboBox::findData(...)`) so I have had to provide a: `(QList<QListWidgetItem*>) findData(const QListWidget&, const QVariant&, const int role = Qt::UserRole) const; method to fill in this gap and to replace the previous `QListWidget::find(...)` that examined each item's text. Also removed local `(QString) profile` from: `(void) dlgConnectionProfiles::slot_item_clicked(QListWidgetItem*)` as it is merely a redundent duplicate of another local `(QString) profile_name`. Renamed local `(QListWidgetItem*) pM` from: `(void) dlgConnectionProfiles::fillout_form()` to: `(QListWidgetItem*) pItem` as that reflects the name used throughout the rest of the class for this type of variable. Changed a `(QLabel*)::setText(tr(""))` call to the more explicit and less stupid `(QLabel*)::clear()`. Similarly changed a `(QWidget*)::setToolTip("")` to a `(QWidget*)::setToolTip(QString())`. Signed-off-by: Stephen Lyons <slysven@virginmedia.com>
2020-07-01 20:59:42 +01:00
loadCustomProfile(profileName);
} else {
// mProfileList is derived from a filesystem directory, but MacOS is not
// necessarily case preserving for file names so any tests on them
// should be case insensitive
// skip creating icons for default MUDs as they are already created above
if (defaultGames.contains(profileName, Qt::CaseInsensitive)) {
continue;
}
Revise: select profiles by typing the name, keep profile icons (#4440) Restore the text display of the profile name. This is so that the keyboard selection of profiles beginning with a particular letter works again. This will close issue #4283 . This also sets it to the minimum size trying to hide it by setting the font size to 1 - however that still show something (and a font size of zero is NOT valid). Manipulating the QPalette of the QListWidget does not work well enough in the presence of Styles or StyleSheets to be a viable means of hiding the text either. Also in the `dlgConnection` class: * refactor out some highly repetitive code that sets up the predefined Mud icons. * remove warnings and adopts a change introduced into Qt 5.15 for the QLabel::pixmap() which now requires a Qt::ReturnByValue argument to prevent an obsolete code warning and to force a QPixmap to be returned by value and not reference... * remove a warning about: QListWidget::setItemSelected(QListWidgetItem*,bool) being deprecated and switches to the recommended: QListWidgetItem::setSelected(bool) Edit: the last item above may have become redundant as further commits amalgamated into this PR may have taken out these lines anyhow! Change the custom icon generator so that there is NOT a dependency on the position in the list for the colours used. This means that the same name will always have the same colours - which will help the user to pick out a particular profile as it will always look the same no matter if other profiles are added or removed. Remove the colour gradient in the custom icon as it is not thought to be useful nowadays. Refactor out the colour icon generator to a single method that is called from more than one place. BugFix: restore odd original way of accessing some QPixmaps This was causing failures in some CI platforms using Qt 5.14. or older. Revise: really frob the custom icon generator This is to try and produce as varied but still readable icons when they are generated entirely on the basis of the MUD name - in a reproducible (though Qt do NOT guarantee repeatable qHash() functionality across Qt versions - so they may not stay *that* constant) manner. Refrob: use a table of fixed colours Using only 15 hues but with 3 different saturation level plus 5 grey-scale one should give enough different icons but not generate them too close to each other for those with normal colour vision... Signed-off-by: Stephen Lyons <slysven@virginmedia.com> Co-authored-by: Kebap <kebap_spam@gmx.net>
2020-12-11 17:53:28 +00:00
// This will instantiate a new QListWidgetItem for the profile:
generateCustomProfile(profileName);
}
}
}
bool dlgConnectionProfiles::hasCustomIcon(const QString& profileName) const
{
return QFileInfo::exists(mudlet::getMudletPath(enums::profileDataItemPath, profileName, qsl("profileicon")));
}
Refactor: remove minute visible name from Connection dialogue icons We have been trying to hide the text associated with the `QListWidgetItems` in the connection dialogue by setting the font size to the minimum of `1` and by setting it's colour to be white. This is not effective when the background is not white - which is likely for a "Dark" desktop environment. The only way to successfully hide the text is, I think, to not have any! However the text was being used programmatically, so the best way to use a `QListWidget` in this mannar is to store the text elsewhere in each item's structure. Fortunately Qt provides for this with the user data functionality which allows multiple data items (based on the `QVariant` class) to be stored within each `QListWidgetItem` using an integer key to denote the type of the data. Ironically the text, icon and other details for each item are ALSO stored in this way - however for non-Qt internal use the lowest integer key that is to be used is `Qt::UserRole` - which I have assigned to the `(const int) dlgConnectionProfiles::csmNameRole` static value. It is quite possible that a redesign of the Connect Profiles dialogue may use this system to store/cache more details about each profile in the future! The only issue with this is the lack of a: `QListWidget::findData(const QVarient&data, int role = Qt::UserRole ...)` method (c.f. `QComboBox::findData(...)`) so I have had to provide a: `(QList<QListWidgetItem*>) findData(const QListWidget&, const QVariant&, const int role = Qt::UserRole) const; method to fill in this gap and to replace the previous `QListWidget::find(...)` that examined each item's text. Also removed local `(QString) profile` from: `(void) dlgConnectionProfiles::slot_item_clicked(QListWidgetItem*)` as it is merely a redundent duplicate of another local `(QString) profile_name`. Renamed local `(QListWidgetItem*) pM` from: `(void) dlgConnectionProfiles::fillout_form()` to: `(QListWidgetItem*) pItem` as that reflects the name used throughout the rest of the class for this type of variable. Changed a `(QLabel*)::setText(tr(""))` call to the more explicit and less stupid `(QLabel*)::clear()`. Similarly changed a `(QWidget*)::setToolTip("")` to a `(QWidget*)::setToolTip(QString())`. Signed-off-by: Stephen Lyons <slysven@virginmedia.com>
2020-07-01 20:59:42 +01:00
void dlgConnectionProfiles::loadCustomProfile(const QString& profileName) const
{
Refactor: remove minute visible name from Connection dialogue icons We have been trying to hide the text associated with the `QListWidgetItems` in the connection dialogue by setting the font size to the minimum of `1` and by setting it's colour to be white. This is not effective when the background is not white - which is likely for a "Dark" desktop environment. The only way to successfully hide the text is, I think, to not have any! However the text was being used programmatically, so the best way to use a `QListWidget` in this mannar is to store the text elsewhere in each item's structure. Fortunately Qt provides for this with the user data functionality which allows multiple data items (based on the `QVariant` class) to be stored within each `QListWidgetItem` using an integer key to denote the type of the data. Ironically the text, icon and other details for each item are ALSO stored in this way - however for non-Qt internal use the lowest integer key that is to be used is `Qt::UserRole` - which I have assigned to the `(const int) dlgConnectionProfiles::csmNameRole` static value. It is quite possible that a redesign of the Connect Profiles dialogue may use this system to store/cache more details about each profile in the future! The only issue with this is the lack of a: `QListWidget::findData(const QVarient&data, int role = Qt::UserRole ...)` method (c.f. `QComboBox::findData(...)`) so I have had to provide a: `(QList<QListWidgetItem*>) findData(const QListWidget&, const QVariant&, const int role = Qt::UserRole) const; method to fill in this gap and to replace the previous `QListWidget::find(...)` that examined each item's text. Also removed local `(QString) profile` from: `(void) dlgConnectionProfiles::slot_item_clicked(QListWidgetItem*)` as it is merely a redundent duplicate of another local `(QString) profile_name`. Renamed local `(QListWidgetItem*) pM` from: `(void) dlgConnectionProfiles::fillout_form()` to: `(QListWidgetItem*) pItem` as that reflects the name used throughout the rest of the class for this type of variable. Changed a `(QLabel*)::setText(tr(""))` call to the more explicit and less stupid `(QLabel*)::clear()`. Similarly changed a `(QWidget*)::setToolTip("")` to a `(QWidget*)::setToolTip(QString())`. Signed-off-by: Stephen Lyons <slysven@virginmedia.com>
2020-07-01 20:59:42 +01:00
auto pItem = new QListWidgetItem();
Revise: select profiles by typing the name, keep profile icons (#4440) Restore the text display of the profile name. This is so that the keyboard selection of profiles beginning with a particular letter works again. This will close issue #4283 . This also sets it to the minimum size trying to hide it by setting the font size to 1 - however that still show something (and a font size of zero is NOT valid). Manipulating the QPalette of the QListWidget does not work well enough in the presence of Styles or StyleSheets to be a viable means of hiding the text either. Also in the `dlgConnection` class: * refactor out some highly repetitive code that sets up the predefined Mud icons. * remove warnings and adopts a change introduced into Qt 5.15 for the QLabel::pixmap() which now requires a Qt::ReturnByValue argument to prevent an obsolete code warning and to force a QPixmap to be returned by value and not reference... * remove a warning about: QListWidget::setItemSelected(QListWidgetItem*,bool) being deprecated and switches to the recommended: QListWidgetItem::setSelected(bool) Edit: the last item above may have become redundant as further commits amalgamated into this PR may have taken out these lines anyhow! Change the custom icon generator so that there is NOT a dependency on the position in the list for the colours used. This means that the same name will always have the same colours - which will help the user to pick out a particular profile as it will always look the same no matter if other profiles are added or removed. Remove the colour gradient in the custom icon as it is not thought to be useful nowadays. Refactor out the colour icon generator to a single method that is called from more than one place. BugFix: restore odd original way of accessing some QPixmaps This was causing failures in some CI platforms using Qt 5.14. or older. Revise: really frob the custom icon generator This is to try and produce as varied but still readable icons when they are generated entirely on the basis of the MUD name - in a reproducible (though Qt do NOT guarantee repeatable qHash() functionality across Qt versions - so they may not stay *that* constant) manner. Refrob: use a table of fixed colours Using only 15 hues but with 3 different saturation level plus 5 grey-scale one should give enough different icons but not generate them too close to each other for those with normal colour vision... Signed-off-by: Stephen Lyons <slysven@virginmedia.com> Co-authored-by: Kebap <kebap_spam@gmx.net>
2020-12-11 17:53:28 +00:00
setItemName(pItem, profileName);
setCustomIcon(profileName, pItem);
auto description = getDescription(profileName);
if (!description.isEmpty()) {
pItem->setToolTip(utils::richText(description));
}
listWidget_profiles->addItem(pItem);
}
Revise: select profiles by typing the name, keep profile icons (#4440) Restore the text display of the profile name. This is so that the keyboard selection of profiles beginning with a particular letter works again. This will close issue #4283 . This also sets it to the minimum size trying to hide it by setting the font size to 1 - however that still show something (and a font size of zero is NOT valid). Manipulating the QPalette of the QListWidget does not work well enough in the presence of Styles or StyleSheets to be a viable means of hiding the text either. Also in the `dlgConnection` class: * refactor out some highly repetitive code that sets up the predefined Mud icons. * remove warnings and adopts a change introduced into Qt 5.15 for the QLabel::pixmap() which now requires a Qt::ReturnByValue argument to prevent an obsolete code warning and to force a QPixmap to be returned by value and not reference... * remove a warning about: QListWidget::setItemSelected(QListWidgetItem*,bool) being deprecated and switches to the recommended: QListWidgetItem::setSelected(bool) Edit: the last item above may have become redundant as further commits amalgamated into this PR may have taken out these lines anyhow! Change the custom icon generator so that there is NOT a dependency on the position in the list for the colours used. This means that the same name will always have the same colours - which will help the user to pick out a particular profile as it will always look the same no matter if other profiles are added or removed. Remove the colour gradient in the custom icon as it is not thought to be useful nowadays. Refactor out the colour icon generator to a single method that is called from more than one place. BugFix: restore odd original way of accessing some QPixmaps This was causing failures in some CI platforms using Qt 5.14. or older. Revise: really frob the custom icon generator This is to try and produce as varied but still readable icons when they are generated entirely on the basis of the MUD name - in a reproducible (though Qt do NOT guarantee repeatable qHash() functionality across Qt versions - so they may not stay *that* constant) manner. Refrob: use a table of fixed colours Using only 15 hues but with 3 different saturation level plus 5 grey-scale one should give enough different icons but not generate them too close to each other for those with normal colour vision... Signed-off-by: Stephen Lyons <slysven@virginmedia.com> Co-authored-by: Kebap <kebap_spam@gmx.net>
2020-12-11 17:53:28 +00:00
void dlgConnectionProfiles::setCustomIcon(const QString& profileName, QListWidgetItem* profile) const
{
auto profileIconPath = mudlet::getMudletPath(enums::profileDataItemPath, profileName, qsl("profileicon"));
auto icon = QIcon(QPixmap(profileIconPath).scaled(QSize(120, 30), Qt::IgnoreAspectRatio, Qt::SmoothTransformation).copy());
profile->setIcon(icon);
}
// When a profile is renamed, migrate password storage to the new profile
void dlgConnectionProfiles::migrateSecuredPassword(const QString& oldProfile, const QString& newProfile)
{
const auto& password = character_password_entry->text().trimmed();
deleteSecurePassword(oldProfile);
2025-09-13 14:17:13 +07:00
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 06:44:38 -04:00
// Only store the password if it's not empty
if (!password.isEmpty()) {
writeSecurePassword(newProfile, password);
}
}
template <typename L>
void dlgConnectionProfiles::loadSecuredPassword(const QString& profile, L callback)
{
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 06:44:38 -04:00
// Use async API for QtKeychain integration with file fallback
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 16:42:09 +12:00
auto* credManager = new CredentialManager(this);
2025-09-13 14:17:13 +07:00
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 06:44:38 -04:00
credManager->retrievePassword(profile, "character", [credManager, callback = std::move(callback)](bool success, const QString& password, const QString& errorMessage) {
if (success) {
callback(password);
QString passwordCopy = password; // Make a copy for secure clearing
SecureStringUtils::secureStringClear(passwordCopy);
} else {
if (!errorMessage.isEmpty()) {
qDebug() << "dlgConnectionProfiles: Failed to retrieve password:" << errorMessage;
}
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 06:44:38 -04:00
callback(QString()); // Call with empty string on failure
}
2025-09-13 14:17:13 +07:00
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 06:44:38 -04:00
// Clean up the credential manager
credManager->deleteLater();
});
}
std::optional<QColor> getCustomColor(const QString& profileName)
{
auto profileColorPath = mudlet::getMudletPath(enums::profileDataItemPath, profileName, qsl("profilecolor"));
if (QFileInfo::exists(profileColorPath)) {
QFile file(profileColorPath);
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
return std::nullopt;
}
QTextStream in(&file);
const QString colorString = in.readLine();
QColor color(colorString);
if (color.isValid()) {
return {color};
}
}
return std::nullopt;
}
Revise: select profiles by typing the name, keep profile icons (#4440) Restore the text display of the profile name. This is so that the keyboard selection of profiles beginning with a particular letter works again. This will close issue #4283 . This also sets it to the minimum size trying to hide it by setting the font size to 1 - however that still show something (and a font size of zero is NOT valid). Manipulating the QPalette of the QListWidget does not work well enough in the presence of Styles or StyleSheets to be a viable means of hiding the text either. Also in the `dlgConnection` class: * refactor out some highly repetitive code that sets up the predefined Mud icons. * remove warnings and adopts a change introduced into Qt 5.15 for the QLabel::pixmap() which now requires a Qt::ReturnByValue argument to prevent an obsolete code warning and to force a QPixmap to be returned by value and not reference... * remove a warning about: QListWidget::setItemSelected(QListWidgetItem*,bool) being deprecated and switches to the recommended: QListWidgetItem::setSelected(bool) Edit: the last item above may have become redundant as further commits amalgamated into this PR may have taken out these lines anyhow! Change the custom icon generator so that there is NOT a dependency on the position in the list for the colours used. This means that the same name will always have the same colours - which will help the user to pick out a particular profile as it will always look the same no matter if other profiles are added or removed. Remove the colour gradient in the custom icon as it is not thought to be useful nowadays. Refactor out the colour icon generator to a single method that is called from more than one place. BugFix: restore odd original way of accessing some QPixmaps This was causing failures in some CI platforms using Qt 5.14. or older. Revise: really frob the custom icon generator This is to try and produce as varied but still readable icons when they are generated entirely on the basis of the MUD name - in a reproducible (though Qt do NOT guarantee repeatable qHash() functionality across Qt versions - so they may not stay *that* constant) manner. Refrob: use a table of fixed colours Using only 15 hues but with 3 different saturation level plus 5 grey-scale one should give enough different icons but not generate them too close to each other for those with normal colour vision... Signed-off-by: Stephen Lyons <slysven@virginmedia.com> Co-authored-by: Kebap <kebap_spam@gmx.net>
2020-12-11 17:53:28 +00:00
void dlgConnectionProfiles::generateCustomProfile(const QString& profileName) const
{
Refactor: remove minute visible name from Connection dialogue icons We have been trying to hide the text associated with the `QListWidgetItems` in the connection dialogue by setting the font size to the minimum of `1` and by setting it's colour to be white. This is not effective when the background is not white - which is likely for a "Dark" desktop environment. The only way to successfully hide the text is, I think, to not have any! However the text was being used programmatically, so the best way to use a `QListWidget` in this mannar is to store the text elsewhere in each item's structure. Fortunately Qt provides for this with the user data functionality which allows multiple data items (based on the `QVariant` class) to be stored within each `QListWidgetItem` using an integer key to denote the type of the data. Ironically the text, icon and other details for each item are ALSO stored in this way - however for non-Qt internal use the lowest integer key that is to be used is `Qt::UserRole` - which I have assigned to the `(const int) dlgConnectionProfiles::csmNameRole` static value. It is quite possible that a redesign of the Connect Profiles dialogue may use this system to store/cache more details about each profile in the future! The only issue with this is the lack of a: `QListWidget::findData(const QVarient&data, int role = Qt::UserRole ...)` method (c.f. `QComboBox::findData(...)`) so I have had to provide a: `(QList<QListWidgetItem*>) findData(const QListWidget&, const QVariant&, const int role = Qt::UserRole) const; method to fill in this gap and to replace the previous `QListWidget::find(...)` that examined each item's text. Also removed local `(QString) profile` from: `(void) dlgConnectionProfiles::slot_item_clicked(QListWidgetItem*)` as it is merely a redundent duplicate of another local `(QString) profile_name`. Renamed local `(QListWidgetItem*) pM` from: `(void) dlgConnectionProfiles::fillout_form()` to: `(QListWidgetItem*) pItem` as that reflects the name used throughout the rest of the class for this type of variable. Changed a `(QLabel*)::setText(tr(""))` call to the more explicit and less stupid `(QLabel*)::clear()`. Similarly changed a `(QWidget*)::setToolTip("")` to a `(QWidget*)::setToolTip(QString())`. Signed-off-by: Stephen Lyons <slysven@virginmedia.com>
2020-07-01 20:59:42 +01:00
auto pItem = new QListWidgetItem();
Revise: select profiles by typing the name, keep profile icons (#4440) Restore the text display of the profile name. This is so that the keyboard selection of profiles beginning with a particular letter works again. This will close issue #4283 . This also sets it to the minimum size trying to hide it by setting the font size to 1 - however that still show something (and a font size of zero is NOT valid). Manipulating the QPalette of the QListWidget does not work well enough in the presence of Styles or StyleSheets to be a viable means of hiding the text either. Also in the `dlgConnection` class: * refactor out some highly repetitive code that sets up the predefined Mud icons. * remove warnings and adopts a change introduced into Qt 5.15 for the QLabel::pixmap() which now requires a Qt::ReturnByValue argument to prevent an obsolete code warning and to force a QPixmap to be returned by value and not reference... * remove a warning about: QListWidget::setItemSelected(QListWidgetItem*,bool) being deprecated and switches to the recommended: QListWidgetItem::setSelected(bool) Edit: the last item above may have become redundant as further commits amalgamated into this PR may have taken out these lines anyhow! Change the custom icon generator so that there is NOT a dependency on the position in the list for the colours used. This means that the same name will always have the same colours - which will help the user to pick out a particular profile as it will always look the same no matter if other profiles are added or removed. Remove the colour gradient in the custom icon as it is not thought to be useful nowadays. Refactor out the colour icon generator to a single method that is called from more than one place. BugFix: restore odd original way of accessing some QPixmaps This was causing failures in some CI platforms using Qt 5.14. or older. Revise: really frob the custom icon generator This is to try and produce as varied but still readable icons when they are generated entirely on the basis of the MUD name - in a reproducible (though Qt do NOT guarantee repeatable qHash() functionality across Qt versions - so they may not stay *that* constant) manner. Refrob: use a table of fixed colours Using only 15 hues but with 3 different saturation level plus 5 grey-scale one should give enough different icons but not generate them too close to each other for those with normal colour vision... Signed-off-by: Stephen Lyons <slysven@virginmedia.com> Co-authored-by: Kebap <kebap_spam@gmx.net>
2020-12-11 17:53:28 +00:00
setItemName(pItem, profileName);
pItem->setIcon(customIcon(profileName, getCustomColor(profileName)));
listWidget_profiles->addItem(pItem);
}
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
// fillout_form() destroys and rebuilds every item, so callers that have let the
// event loop run cannot hold on to one.
void dlgConnectionProfiles::setIconOfListedProfile(const QString& profileName, const QIcon& icon) const
{
const auto pItems = findData(*listWidget_profiles, profileName, csmNameRole);
if (pItems.isEmpty()) {
return;
}
pItems.first()->setIcon(icon);
}
// Empty when nothing is selected. The context-menu actions re-check rather than
// trust slot_profileContextMenu(): menu.exec() runs a nested event loop, and the
// profile-copy completion handler calls fillout_form() from it, which can clear
// the selection - or leave a different profile current, in which case the action
// still mis-targets. Only the crash of acting on nothing is handled here.
QString dlgConnectionProfiles::selectedProfileName() const
{
const auto* pItem = listWidget_profiles->currentItem();
return pItem ? pItem->data(csmNameRole).toString() : QString();
}
Infrastructure: tidy up naming of SLOT methods and their usage - Part 5 (last) (#6266) When Qt's slot/signal system is used to invoke a method there is some overhead - so it makes sense to ensure developers can spot all such methods (functions). We have tried to do this with a `slot_` prefix to the methods we create but it has not been applied uniformly. This PR is intended to conclude helping with this by more rigorously doing so - the names changed herein should all follow a `slot_`*camelCaseMethodName* style. In addition some methods were detected that are not currently used or which are not actually used via the signal/slot system, these have been commented out or have had the prefix removed and the declaration in the relevant header file moved as appropriate. There were two similar sets of (not prefixed with `slot_`) slots in the Profile preferences class that adjusted the colour settings separately for the main console and the mapper the latter had the same names but with a `2` suffix. To made it more clear I have changed them to include `Map` in their names instead. Also, the NON-slot method: `(void) dlgProfilePreferences::setColor(QPushButton*, QColor&, bool)` has been renamed to: `(void) dlgProfilePreferences::setButtonAndProfileColor(QPushButton*, QColor&, bool)` so that it is clearly distinguishable from built in Qt methods that are also called `setColor` - though which do have different signatures! For reference the changes made are: * `TConsole::slot_stop_all_triggers(...)` ==> `TConsole::slot_stopAllItems(...)` * `dlgConnectionProfiles::slot_copy_profile()` ==> `dlgConnectionProfiles::slot_copyProfile()` * `dlgConnectionProfiles::slot_copy_profilesettings_only()` ==> `dlgConnectionProfiles::slot_copyOnlySettingsOfProfile()` * `dlgConnectionProfiles::slot_deleteprofile_check(...)` ==> `dlgConnectionProfiles::slot_deleteProfileCheck(...)` * `dlgConnectionProfiles::slot_password_deleted(...)` ==> `dlgConnectionProfiles::slot_passwordDeleted(...)` * `dlgConnectionProfiles::slot_password_saved(...) ==> `dlgConnectionProfiles::slot_passwordSaved(...)` * `dlgConnectionProfiles::slot_profile_menu(...)` ==> `dlgConnectionProfiles::slot_profileContextMenu(...)` * `dlgConnectionProfiles::slot_reset_custom_icon()` ==> `dlgConnectionProfiles::slot_resetCustomIcon()` * `dlgConnectionProfiles::slot_set_custom_icon()` ==> `dlgConnectionProfiles::slot_setCustomIcon()` * `dlgConnectionProfiles::slot_set_custom_color()` ==> `dlgConnectionProfiles::slot_setCustomColor()` * `dlgConnectionProfiles::slot_update_autologin(...)` ==> `dlgConnectionProfiles::slot_updateAutoConnect(...)` * `dlgConnectionProfiles::slot_update_autoreconnect(...)` ==> `dlgConnectionProfiles::slot_updateAutoReconnect(...)` * `dlgConnectionProfiles::slot_update_description()` ==> `dlgConnectionProfiles::slot_updateDescription()` * `dlgConnectionProfiles::slot_update_discord_optin(...)` ==> `dlgConnectionProfiles::slot_updateDiscordOptIn(...)` * `dlgProfilePreferences::copyMap()` ==> `dlgProfilePreferences::slot_copyMap()` * `dlgProfilePreferences::downloadMap()` ==> `dlgProfilePreferences::slot_downloadMap()` * `dlgProfilePreferences::hideActionLabel()` ==> `dlgProfilePreferences::slot_hideActionLabel()` * `dlgProfilePreferences::loadMap()` ==> `dlgProfilePreferences::slot_loadMap()` * `dlgProfilePreferences::resetColors()` ==> `dlgProfilePreferences::slot_resetColors()` * `dlgProfilePreferences::resetColors2()` ==> `dlgProfilePreferences::slot_resetMapColors()` * `dlgProfilePreferences::saveMap()` ==> `dlgProfilePreferences::slot_saveMap()` * `dlgProfilePreferences::setBgColor()` ==> `dlgProfilePreferences::slot_setBgColor()` * `dlgProfilePreferences::setBgColor2()` ==> `dlgProfilePreferences::slot_setMapBgColor()` * `dlgProfilePreferences::setColorBlack()` ==> `dlgProfilePreferences::slot_setColorBlack()` * `dlgProfilePreferences::setColorBlack2()` ==> `dlgProfilePreferences::slot_setMapColorBlack()` * `dlgProfilePreferences::setColorBlue()` ==> `dlgProfilePreferences::slot_setColorBlue()` * `dlgProfilePreferences::setColorCyan()` ==> `dlgProfilePreferences::slot_setColorCyan()` * `dlgProfilePreferences::setColorBlue2()` ==> `dlgProfilePreferences::slot_setMapColorBlue()` * `dlgProfilePreferences::setColorCyan2()` ==> `dlgProfilePreferences::slot_setMapColorCyan()` * `dlgProfilePreferences::setColorGreen()` ==> `dlgProfilePreferences::slot_setColorGreen()` * `dlgProfilePreferences::setColorGreen2()` ==> `dlgProfilePreferences::slot_setMapColorGreen()` * `dlgProfilePreferences::setColorLightBlack()` ==> `dlgProfilePreferences::slot_setColorLightBlack()` * `dlgProfilePreferences::setColorLightBlack2()` ==> `dlgProfilePreferences::slot_setMapColorLightBlack()` * `dlgProfilePreferences::setColorLightBlue()` ==> `dlgProfilePreferences::slot_setColorLightBlue()` * `dlgProfilePreferences::setColorLightBlue2()` ==> `dlgProfilePreferences::slot_setMapColorLightBlue()` * `dlgProfilePreferences::setColorLightCyan()` ==> `dlgProfilePreferences::slot_setColorLightCyan()` * `dlgProfilePreferences::setColorLightCyan2()` ==> `dlgProfilePreferences::slot_setMapColorLightCyan()` * `dlgProfilePreferences::setColorLightGreen()` ==> `dlgProfilePreferences::slot_setColorLightGreen()` * `dlgProfilePreferences::setColorLightGreen2()` ==> `dlgProfilePreferences::slot_setMapColorLightGreen()` * `dlgProfilePreferences::setColorLightMagenta()` ==> `dlgProfilePreferences::slot_setColorLightMagenta()` * `dlgProfilePreferences::setColorLightMagenta2()` ==> `dlgProfilePreferences::slot_setMapColorLightMagenta()` * `dlgProfilePreferences::setColorLightRed()` ==> `dlgProfilePreferences::slot_setColorLightRed()` * `dlgProfilePreferences::setColorLightRed2()` ==> `dlgProfilePreferences::slot_setMapColorLightRed()` * `dlgProfilePreferences::setColorLightWhite()` ==> `dlgProfilePreferences::slot_setColorLightWhite()` * `dlgProfilePreferences::setColorLightWhite2()` ==> `dlgProfilePreferences::slot_setMapColorLightWhite()` * `dlgProfilePreferences::setColorLightYellow()` ==> `dlgProfilePreferences::slot_setColorLightYellow()` * `dlgProfilePreferences::setColorLightYellow2()` ==> `dlgProfilePreferences::slot_setMapColorLightYellow()` * `dlgProfilePreferences::setColorMagenta()` ==> `dlgProfilePreferences::slot_setColorMagenta()` * `dlgProfilePreferences::setColorMagenta2()` ==> `dlgProfilePreferences::slot_setMapColorMagenta()` * `dlgProfilePreferences::setColorRed2()` ==> `dlgProfilePreferences::slot_setMapColorRed()` * `dlgProfilePreferences::setColorRed()` ==> `dlgProfilePreferences::slot_setColorRed()` * `dlgProfilePreferences::setColorWhite()` ==> `dlgProfilePreferences::slot_setColorWhite()` * `dlgProfilePreferences::setColorWhite2()` ==> `dlgProfilePreferences::slot_setMapColorWhite()` * `dlgProfilePreferences::setColorYellow()` ==> `dlgProfilePreferences::slot_setColorYellow()` * `dlgProfilePreferences::setColorYellow2()` ==> `dlgProfilePreferences::slot_setMapColorYellow()` * `dlgProfilePreferences::setCommandBgColor()` ==> `dlgProfilePreferences::slot_setCommandBgColor()` * `dlgProfilePreferences::setCommandFgColor()` ==> `dlgProfilePreferences::slot_setCommandFgColor()` * `dlgProfilePreferences::setCommandLineBgColor()` ==> `dlgProfilePreferences::slot_setCommandLineBgColor()` * `dlgProfilePreferences::setCommandLineFgColor()` ==> `dlgProfilePreferences::slot_setCommandLineFgColor()` * `dlgProfilePreferences::setDisplayFont()` ==> `dlgProfilePreferences::slot_setDisplayFont()` * `dlgProfilePreferences::setFgColor()` ==> `dlgProfilePreferences::slot_setFgColor()` * `dlgProfilePreferences::setFgColor2()` ==> `dlgProfilePreferences::slot_setMapExitsColor()` * `dlgProfilePreferences::setFontSize()` ==> `dlgProfilePreferences::slot_setFontSize()` * `dlgProfilePreferences::setMapInfoBackground()` ==> `dlgProfilePreferences::slot_setMapInfoBgColor()` * `dlgProfilePreferences::setRoomBorderColor()` ==> `dlgProfilePreferences::slot_setMapRoomBorderColor()` * `dlgProfilePreferences::slot_script_selected(...)` ==> `dlgProfilePreferences::slot_scriptSelected(...)` * `dlgProfilePreferences::slot_theme_selected(...)` ==> `dlgProfilePreferences::slot_themeSelected(...)` * `dlgRoomSymbol::colorRejected()` ==> `dlgRoomSymbol::slot_colorRejected()` * `dlgRoomSymbol::colorSelected(...)` ==> `dlgRoomSymbol::slot_colorSelected(...)` * `dlgRoomSymbol::currentColorChanged(...)` ==> `dlgRoomSymbol::slot_currentColorChanged(...)` * `dlgRoomSymbol::openColorSelector()` ==> `dlgRoomSymbol::slot_openColorSelector()` * `dlgRoomSymbol::resetColor()` ==> `dlgRoomSymbol::slot_resetColors()` * `dlgRoomSymbol::updatePreview()` ==> `dlgRoomSymbol::slot_updatePreview()` * `dlgTriggerEditor::slot_show_vars()` ==> `dlgTriggerEditor::slot_showVariables()` * `dlgTriggerEditor::slot_var_changed(...)` ==> `dlgTriggerEditor::slot_variableChanged(...)` * `dlgTriggerEditor::slot_var_selected(...)` ==> `dlgTriggerEditor::slot_variableSelected(...)` * `mudlet::slot_check_manual_update()` ==> `mudlet::slot_manualUpdateCheck()` * `mudlet::slot_close_current_profile()` ==> `mudlet::slot_closeCurrentProfile()` * `mudlet::slot_close_profile_requested(...)` ==> `mudlet::slot_closeProfileRequested(...)` * `mudlet::slot_connection_dlg_finished(...)` ==> `mudlet::slot_connectionDialogueFinished(...)` * `mudlet::slot_module_manager()` ==> `mudlet::slot_moduleManager()` * `mudlet::slot_mudlet_discord()` ==> `mudlet::slot_mudletDiscord()` * `mudlet::slot_multi_view(...)` ==> `mudlet::slot_multiView(const bool state)` * `mudlet::slot_package_manager()` ==> `mudlet::slot_packageManager()` * `mudlet::slot_package_exporter()` ==> `mudlet::slot_packageExporter()` * `mudlet::slot_timer_fires()` ==> `mudlet::slot_timerFires()` * `mudlet::slot_toggle_multi_view()` ==> `mudlet::slot_toggleMultiView()` Also the names for these in particular have been changed to make more sense: * `dlgProfilePreferences::slot_chooseProfilesChanged()` ==> `dlgProfilePreferences::slot_chosenProfilesChanged()` * `dlgProfilePreferences::slot_editor_tab_selected(...)` ==> `dlgProfilePreferences::slot_tabChanged(...)` * `dlgProfilePreferences::slot_passwords_location_changed(...)` ==> `dlgProfilePreferences::slot_passwordStorageLocationChanged(...)` * `dlgProfilePreferences::slot_save_and_exit()` ==> `dlgProfilePreferences::slot_saveAndClose()` * `dlgTriggerEditor::slot_toggleHiddenVar(...)` ==> `dlgTriggerEditor::slot_hideVariable(...)` - changed to distinguish it from `slot_toggleHiddenVariables(...)` * `dlgTriggerEditor::slot_item_selected_save(...)` ==> `dlgTriggerEditor::slot_saveSelectedItem(...)` * `mudlet::slot_discord()` ==> `mudlet::slot_profileDiscord()` - changed to distinguish it from `slot_mudletDiscord()` Not currently used and commented out: * `dlgProfilePreferences::setCommandLineFont()` ==> `dlgProfilePreferences::slot_setCommandLineFont()` Signed-off-by: Stephen Lyons <slysven@virginmedia.com>
2022-08-29 14:27:13 +02:00
void dlgConnectionProfiles::slot_profileContextMenu(QPoint pos)
{
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
// "My games" on a fresh install lists nothing, so nothing is current
const auto profileName = selectedProfileName();
if (profileName.isEmpty()) {
return;
}
const QPoint globalPos = listWidget_profiles->mapToGlobal(pos);
QMenu menu;
if (hasCustomIcon(profileName)) {
//: Reset the custom picture for this profile in the connection dialog and show the default one instead
menu.addAction(tr("Reset icon"), this, &dlgConnectionProfiles::slot_resetCustomIcon);
} else {
menu.addAction(QIcon(":/icons/mudlet_main_16px.png"),
//: Set a custom picture to show for the profile in the connection dialog
tr("Set custom icon"),
this,
Infrastructure: tidy up naming of SLOT methods and their usage - Part 5 (last) (#6266) When Qt's slot/signal system is used to invoke a method there is some overhead - so it makes sense to ensure developers can spot all such methods (functions). We have tried to do this with a `slot_` prefix to the methods we create but it has not been applied uniformly. This PR is intended to conclude helping with this by more rigorously doing so - the names changed herein should all follow a `slot_`*camelCaseMethodName* style. In addition some methods were detected that are not currently used or which are not actually used via the signal/slot system, these have been commented out or have had the prefix removed and the declaration in the relevant header file moved as appropriate. There were two similar sets of (not prefixed with `slot_`) slots in the Profile preferences class that adjusted the colour settings separately for the main console and the mapper the latter had the same names but with a `2` suffix. To made it more clear I have changed them to include `Map` in their names instead. Also, the NON-slot method: `(void) dlgProfilePreferences::setColor(QPushButton*, QColor&, bool)` has been renamed to: `(void) dlgProfilePreferences::setButtonAndProfileColor(QPushButton*, QColor&, bool)` so that it is clearly distinguishable from built in Qt methods that are also called `setColor` - though which do have different signatures! For reference the changes made are: * `TConsole::slot_stop_all_triggers(...)` ==> `TConsole::slot_stopAllItems(...)` * `dlgConnectionProfiles::slot_copy_profile()` ==> `dlgConnectionProfiles::slot_copyProfile()` * `dlgConnectionProfiles::slot_copy_profilesettings_only()` ==> `dlgConnectionProfiles::slot_copyOnlySettingsOfProfile()` * `dlgConnectionProfiles::slot_deleteprofile_check(...)` ==> `dlgConnectionProfiles::slot_deleteProfileCheck(...)` * `dlgConnectionProfiles::slot_password_deleted(...)` ==> `dlgConnectionProfiles::slot_passwordDeleted(...)` * `dlgConnectionProfiles::slot_password_saved(...) ==> `dlgConnectionProfiles::slot_passwordSaved(...)` * `dlgConnectionProfiles::slot_profile_menu(...)` ==> `dlgConnectionProfiles::slot_profileContextMenu(...)` * `dlgConnectionProfiles::slot_reset_custom_icon()` ==> `dlgConnectionProfiles::slot_resetCustomIcon()` * `dlgConnectionProfiles::slot_set_custom_icon()` ==> `dlgConnectionProfiles::slot_setCustomIcon()` * `dlgConnectionProfiles::slot_set_custom_color()` ==> `dlgConnectionProfiles::slot_setCustomColor()` * `dlgConnectionProfiles::slot_update_autologin(...)` ==> `dlgConnectionProfiles::slot_updateAutoConnect(...)` * `dlgConnectionProfiles::slot_update_autoreconnect(...)` ==> `dlgConnectionProfiles::slot_updateAutoReconnect(...)` * `dlgConnectionProfiles::slot_update_description()` ==> `dlgConnectionProfiles::slot_updateDescription()` * `dlgConnectionProfiles::slot_update_discord_optin(...)` ==> `dlgConnectionProfiles::slot_updateDiscordOptIn(...)` * `dlgProfilePreferences::copyMap()` ==> `dlgProfilePreferences::slot_copyMap()` * `dlgProfilePreferences::downloadMap()` ==> `dlgProfilePreferences::slot_downloadMap()` * `dlgProfilePreferences::hideActionLabel()` ==> `dlgProfilePreferences::slot_hideActionLabel()` * `dlgProfilePreferences::loadMap()` ==> `dlgProfilePreferences::slot_loadMap()` * `dlgProfilePreferences::resetColors()` ==> `dlgProfilePreferences::slot_resetColors()` * `dlgProfilePreferences::resetColors2()` ==> `dlgProfilePreferences::slot_resetMapColors()` * `dlgProfilePreferences::saveMap()` ==> `dlgProfilePreferences::slot_saveMap()` * `dlgProfilePreferences::setBgColor()` ==> `dlgProfilePreferences::slot_setBgColor()` * `dlgProfilePreferences::setBgColor2()` ==> `dlgProfilePreferences::slot_setMapBgColor()` * `dlgProfilePreferences::setColorBlack()` ==> `dlgProfilePreferences::slot_setColorBlack()` * `dlgProfilePreferences::setColorBlack2()` ==> `dlgProfilePreferences::slot_setMapColorBlack()` * `dlgProfilePreferences::setColorBlue()` ==> `dlgProfilePreferences::slot_setColorBlue()` * `dlgProfilePreferences::setColorCyan()` ==> `dlgProfilePreferences::slot_setColorCyan()` * `dlgProfilePreferences::setColorBlue2()` ==> `dlgProfilePreferences::slot_setMapColorBlue()` * `dlgProfilePreferences::setColorCyan2()` ==> `dlgProfilePreferences::slot_setMapColorCyan()` * `dlgProfilePreferences::setColorGreen()` ==> `dlgProfilePreferences::slot_setColorGreen()` * `dlgProfilePreferences::setColorGreen2()` ==> `dlgProfilePreferences::slot_setMapColorGreen()` * `dlgProfilePreferences::setColorLightBlack()` ==> `dlgProfilePreferences::slot_setColorLightBlack()` * `dlgProfilePreferences::setColorLightBlack2()` ==> `dlgProfilePreferences::slot_setMapColorLightBlack()` * `dlgProfilePreferences::setColorLightBlue()` ==> `dlgProfilePreferences::slot_setColorLightBlue()` * `dlgProfilePreferences::setColorLightBlue2()` ==> `dlgProfilePreferences::slot_setMapColorLightBlue()` * `dlgProfilePreferences::setColorLightCyan()` ==> `dlgProfilePreferences::slot_setColorLightCyan()` * `dlgProfilePreferences::setColorLightCyan2()` ==> `dlgProfilePreferences::slot_setMapColorLightCyan()` * `dlgProfilePreferences::setColorLightGreen()` ==> `dlgProfilePreferences::slot_setColorLightGreen()` * `dlgProfilePreferences::setColorLightGreen2()` ==> `dlgProfilePreferences::slot_setMapColorLightGreen()` * `dlgProfilePreferences::setColorLightMagenta()` ==> `dlgProfilePreferences::slot_setColorLightMagenta()` * `dlgProfilePreferences::setColorLightMagenta2()` ==> `dlgProfilePreferences::slot_setMapColorLightMagenta()` * `dlgProfilePreferences::setColorLightRed()` ==> `dlgProfilePreferences::slot_setColorLightRed()` * `dlgProfilePreferences::setColorLightRed2()` ==> `dlgProfilePreferences::slot_setMapColorLightRed()` * `dlgProfilePreferences::setColorLightWhite()` ==> `dlgProfilePreferences::slot_setColorLightWhite()` * `dlgProfilePreferences::setColorLightWhite2()` ==> `dlgProfilePreferences::slot_setMapColorLightWhite()` * `dlgProfilePreferences::setColorLightYellow()` ==> `dlgProfilePreferences::slot_setColorLightYellow()` * `dlgProfilePreferences::setColorLightYellow2()` ==> `dlgProfilePreferences::slot_setMapColorLightYellow()` * `dlgProfilePreferences::setColorMagenta()` ==> `dlgProfilePreferences::slot_setColorMagenta()` * `dlgProfilePreferences::setColorMagenta2()` ==> `dlgProfilePreferences::slot_setMapColorMagenta()` * `dlgProfilePreferences::setColorRed2()` ==> `dlgProfilePreferences::slot_setMapColorRed()` * `dlgProfilePreferences::setColorRed()` ==> `dlgProfilePreferences::slot_setColorRed()` * `dlgProfilePreferences::setColorWhite()` ==> `dlgProfilePreferences::slot_setColorWhite()` * `dlgProfilePreferences::setColorWhite2()` ==> `dlgProfilePreferences::slot_setMapColorWhite()` * `dlgProfilePreferences::setColorYellow()` ==> `dlgProfilePreferences::slot_setColorYellow()` * `dlgProfilePreferences::setColorYellow2()` ==> `dlgProfilePreferences::slot_setMapColorYellow()` * `dlgProfilePreferences::setCommandBgColor()` ==> `dlgProfilePreferences::slot_setCommandBgColor()` * `dlgProfilePreferences::setCommandFgColor()` ==> `dlgProfilePreferences::slot_setCommandFgColor()` * `dlgProfilePreferences::setCommandLineBgColor()` ==> `dlgProfilePreferences::slot_setCommandLineBgColor()` * `dlgProfilePreferences::setCommandLineFgColor()` ==> `dlgProfilePreferences::slot_setCommandLineFgColor()` * `dlgProfilePreferences::setDisplayFont()` ==> `dlgProfilePreferences::slot_setDisplayFont()` * `dlgProfilePreferences::setFgColor()` ==> `dlgProfilePreferences::slot_setFgColor()` * `dlgProfilePreferences::setFgColor2()` ==> `dlgProfilePreferences::slot_setMapExitsColor()` * `dlgProfilePreferences::setFontSize()` ==> `dlgProfilePreferences::slot_setFontSize()` * `dlgProfilePreferences::setMapInfoBackground()` ==> `dlgProfilePreferences::slot_setMapInfoBgColor()` * `dlgProfilePreferences::setRoomBorderColor()` ==> `dlgProfilePreferences::slot_setMapRoomBorderColor()` * `dlgProfilePreferences::slot_script_selected(...)` ==> `dlgProfilePreferences::slot_scriptSelected(...)` * `dlgProfilePreferences::slot_theme_selected(...)` ==> `dlgProfilePreferences::slot_themeSelected(...)` * `dlgRoomSymbol::colorRejected()` ==> `dlgRoomSymbol::slot_colorRejected()` * `dlgRoomSymbol::colorSelected(...)` ==> `dlgRoomSymbol::slot_colorSelected(...)` * `dlgRoomSymbol::currentColorChanged(...)` ==> `dlgRoomSymbol::slot_currentColorChanged(...)` * `dlgRoomSymbol::openColorSelector()` ==> `dlgRoomSymbol::slot_openColorSelector()` * `dlgRoomSymbol::resetColor()` ==> `dlgRoomSymbol::slot_resetColors()` * `dlgRoomSymbol::updatePreview()` ==> `dlgRoomSymbol::slot_updatePreview()` * `dlgTriggerEditor::slot_show_vars()` ==> `dlgTriggerEditor::slot_showVariables()` * `dlgTriggerEditor::slot_var_changed(...)` ==> `dlgTriggerEditor::slot_variableChanged(...)` * `dlgTriggerEditor::slot_var_selected(...)` ==> `dlgTriggerEditor::slot_variableSelected(...)` * `mudlet::slot_check_manual_update()` ==> `mudlet::slot_manualUpdateCheck()` * `mudlet::slot_close_current_profile()` ==> `mudlet::slot_closeCurrentProfile()` * `mudlet::slot_close_profile_requested(...)` ==> `mudlet::slot_closeProfileRequested(...)` * `mudlet::slot_connection_dlg_finished(...)` ==> `mudlet::slot_connectionDialogueFinished(...)` * `mudlet::slot_module_manager()` ==> `mudlet::slot_moduleManager()` * `mudlet::slot_mudlet_discord()` ==> `mudlet::slot_mudletDiscord()` * `mudlet::slot_multi_view(...)` ==> `mudlet::slot_multiView(const bool state)` * `mudlet::slot_package_manager()` ==> `mudlet::slot_packageManager()` * `mudlet::slot_package_exporter()` ==> `mudlet::slot_packageExporter()` * `mudlet::slot_timer_fires()` ==> `mudlet::slot_timerFires()` * `mudlet::slot_toggle_multi_view()` ==> `mudlet::slot_toggleMultiView()` Also the names for these in particular have been changed to make more sense: * `dlgProfilePreferences::slot_chooseProfilesChanged()` ==> `dlgProfilePreferences::slot_chosenProfilesChanged()` * `dlgProfilePreferences::slot_editor_tab_selected(...)` ==> `dlgProfilePreferences::slot_tabChanged(...)` * `dlgProfilePreferences::slot_passwords_location_changed(...)` ==> `dlgProfilePreferences::slot_passwordStorageLocationChanged(...)` * `dlgProfilePreferences::slot_save_and_exit()` ==> `dlgProfilePreferences::slot_saveAndClose()` * `dlgTriggerEditor::slot_toggleHiddenVar(...)` ==> `dlgTriggerEditor::slot_hideVariable(...)` - changed to distinguish it from `slot_toggleHiddenVariables(...)` * `dlgTriggerEditor::slot_item_selected_save(...)` ==> `dlgTriggerEditor::slot_saveSelectedItem(...)` * `mudlet::slot_discord()` ==> `mudlet::slot_profileDiscord()` - changed to distinguish it from `slot_mudletDiscord()` Not currently used and commented out: * `dlgProfilePreferences::setCommandLineFont()` ==> `dlgProfilePreferences::slot_setCommandLineFont()` Signed-off-by: Stephen Lyons <slysven@virginmedia.com>
2022-08-29 14:27:13 +02:00
&dlgConnectionProfiles::slot_setCustomIcon);
menu.addAction(QIcon(":/icons/mudlet_main_16px.png"),
//: Set a custom color to show for the profile in the connection dialog
tr("Set custom color"),
this,
Infrastructure: tidy up naming of SLOT methods and their usage - Part 5 (last) (#6266) When Qt's slot/signal system is used to invoke a method there is some overhead - so it makes sense to ensure developers can spot all such methods (functions). We have tried to do this with a `slot_` prefix to the methods we create but it has not been applied uniformly. This PR is intended to conclude helping with this by more rigorously doing so - the names changed herein should all follow a `slot_`*camelCaseMethodName* style. In addition some methods were detected that are not currently used or which are not actually used via the signal/slot system, these have been commented out or have had the prefix removed and the declaration in the relevant header file moved as appropriate. There were two similar sets of (not prefixed with `slot_`) slots in the Profile preferences class that adjusted the colour settings separately for the main console and the mapper the latter had the same names but with a `2` suffix. To made it more clear I have changed them to include `Map` in their names instead. Also, the NON-slot method: `(void) dlgProfilePreferences::setColor(QPushButton*, QColor&, bool)` has been renamed to: `(void) dlgProfilePreferences::setButtonAndProfileColor(QPushButton*, QColor&, bool)` so that it is clearly distinguishable from built in Qt methods that are also called `setColor` - though which do have different signatures! For reference the changes made are: * `TConsole::slot_stop_all_triggers(...)` ==> `TConsole::slot_stopAllItems(...)` * `dlgConnectionProfiles::slot_copy_profile()` ==> `dlgConnectionProfiles::slot_copyProfile()` * `dlgConnectionProfiles::slot_copy_profilesettings_only()` ==> `dlgConnectionProfiles::slot_copyOnlySettingsOfProfile()` * `dlgConnectionProfiles::slot_deleteprofile_check(...)` ==> `dlgConnectionProfiles::slot_deleteProfileCheck(...)` * `dlgConnectionProfiles::slot_password_deleted(...)` ==> `dlgConnectionProfiles::slot_passwordDeleted(...)` * `dlgConnectionProfiles::slot_password_saved(...) ==> `dlgConnectionProfiles::slot_passwordSaved(...)` * `dlgConnectionProfiles::slot_profile_menu(...)` ==> `dlgConnectionProfiles::slot_profileContextMenu(...)` * `dlgConnectionProfiles::slot_reset_custom_icon()` ==> `dlgConnectionProfiles::slot_resetCustomIcon()` * `dlgConnectionProfiles::slot_set_custom_icon()` ==> `dlgConnectionProfiles::slot_setCustomIcon()` * `dlgConnectionProfiles::slot_set_custom_color()` ==> `dlgConnectionProfiles::slot_setCustomColor()` * `dlgConnectionProfiles::slot_update_autologin(...)` ==> `dlgConnectionProfiles::slot_updateAutoConnect(...)` * `dlgConnectionProfiles::slot_update_autoreconnect(...)` ==> `dlgConnectionProfiles::slot_updateAutoReconnect(...)` * `dlgConnectionProfiles::slot_update_description()` ==> `dlgConnectionProfiles::slot_updateDescription()` * `dlgConnectionProfiles::slot_update_discord_optin(...)` ==> `dlgConnectionProfiles::slot_updateDiscordOptIn(...)` * `dlgProfilePreferences::copyMap()` ==> `dlgProfilePreferences::slot_copyMap()` * `dlgProfilePreferences::downloadMap()` ==> `dlgProfilePreferences::slot_downloadMap()` * `dlgProfilePreferences::hideActionLabel()` ==> `dlgProfilePreferences::slot_hideActionLabel()` * `dlgProfilePreferences::loadMap()` ==> `dlgProfilePreferences::slot_loadMap()` * `dlgProfilePreferences::resetColors()` ==> `dlgProfilePreferences::slot_resetColors()` * `dlgProfilePreferences::resetColors2()` ==> `dlgProfilePreferences::slot_resetMapColors()` * `dlgProfilePreferences::saveMap()` ==> `dlgProfilePreferences::slot_saveMap()` * `dlgProfilePreferences::setBgColor()` ==> `dlgProfilePreferences::slot_setBgColor()` * `dlgProfilePreferences::setBgColor2()` ==> `dlgProfilePreferences::slot_setMapBgColor()` * `dlgProfilePreferences::setColorBlack()` ==> `dlgProfilePreferences::slot_setColorBlack()` * `dlgProfilePreferences::setColorBlack2()` ==> `dlgProfilePreferences::slot_setMapColorBlack()` * `dlgProfilePreferences::setColorBlue()` ==> `dlgProfilePreferences::slot_setColorBlue()` * `dlgProfilePreferences::setColorCyan()` ==> `dlgProfilePreferences::slot_setColorCyan()` * `dlgProfilePreferences::setColorBlue2()` ==> `dlgProfilePreferences::slot_setMapColorBlue()` * `dlgProfilePreferences::setColorCyan2()` ==> `dlgProfilePreferences::slot_setMapColorCyan()` * `dlgProfilePreferences::setColorGreen()` ==> `dlgProfilePreferences::slot_setColorGreen()` * `dlgProfilePreferences::setColorGreen2()` ==> `dlgProfilePreferences::slot_setMapColorGreen()` * `dlgProfilePreferences::setColorLightBlack()` ==> `dlgProfilePreferences::slot_setColorLightBlack()` * `dlgProfilePreferences::setColorLightBlack2()` ==> `dlgProfilePreferences::slot_setMapColorLightBlack()` * `dlgProfilePreferences::setColorLightBlue()` ==> `dlgProfilePreferences::slot_setColorLightBlue()` * `dlgProfilePreferences::setColorLightBlue2()` ==> `dlgProfilePreferences::slot_setMapColorLightBlue()` * `dlgProfilePreferences::setColorLightCyan()` ==> `dlgProfilePreferences::slot_setColorLightCyan()` * `dlgProfilePreferences::setColorLightCyan2()` ==> `dlgProfilePreferences::slot_setMapColorLightCyan()` * `dlgProfilePreferences::setColorLightGreen()` ==> `dlgProfilePreferences::slot_setColorLightGreen()` * `dlgProfilePreferences::setColorLightGreen2()` ==> `dlgProfilePreferences::slot_setMapColorLightGreen()` * `dlgProfilePreferences::setColorLightMagenta()` ==> `dlgProfilePreferences::slot_setColorLightMagenta()` * `dlgProfilePreferences::setColorLightMagenta2()` ==> `dlgProfilePreferences::slot_setMapColorLightMagenta()` * `dlgProfilePreferences::setColorLightRed()` ==> `dlgProfilePreferences::slot_setColorLightRed()` * `dlgProfilePreferences::setColorLightRed2()` ==> `dlgProfilePreferences::slot_setMapColorLightRed()` * `dlgProfilePreferences::setColorLightWhite()` ==> `dlgProfilePreferences::slot_setColorLightWhite()` * `dlgProfilePreferences::setColorLightWhite2()` ==> `dlgProfilePreferences::slot_setMapColorLightWhite()` * `dlgProfilePreferences::setColorLightYellow()` ==> `dlgProfilePreferences::slot_setColorLightYellow()` * `dlgProfilePreferences::setColorLightYellow2()` ==> `dlgProfilePreferences::slot_setMapColorLightYellow()` * `dlgProfilePreferences::setColorMagenta()` ==> `dlgProfilePreferences::slot_setColorMagenta()` * `dlgProfilePreferences::setColorMagenta2()` ==> `dlgProfilePreferences::slot_setMapColorMagenta()` * `dlgProfilePreferences::setColorRed2()` ==> `dlgProfilePreferences::slot_setMapColorRed()` * `dlgProfilePreferences::setColorRed()` ==> `dlgProfilePreferences::slot_setColorRed()` * `dlgProfilePreferences::setColorWhite()` ==> `dlgProfilePreferences::slot_setColorWhite()` * `dlgProfilePreferences::setColorWhite2()` ==> `dlgProfilePreferences::slot_setMapColorWhite()` * `dlgProfilePreferences::setColorYellow()` ==> `dlgProfilePreferences::slot_setColorYellow()` * `dlgProfilePreferences::setColorYellow2()` ==> `dlgProfilePreferences::slot_setMapColorYellow()` * `dlgProfilePreferences::setCommandBgColor()` ==> `dlgProfilePreferences::slot_setCommandBgColor()` * `dlgProfilePreferences::setCommandFgColor()` ==> `dlgProfilePreferences::slot_setCommandFgColor()` * `dlgProfilePreferences::setCommandLineBgColor()` ==> `dlgProfilePreferences::slot_setCommandLineBgColor()` * `dlgProfilePreferences::setCommandLineFgColor()` ==> `dlgProfilePreferences::slot_setCommandLineFgColor()` * `dlgProfilePreferences::setDisplayFont()` ==> `dlgProfilePreferences::slot_setDisplayFont()` * `dlgProfilePreferences::setFgColor()` ==> `dlgProfilePreferences::slot_setFgColor()` * `dlgProfilePreferences::setFgColor2()` ==> `dlgProfilePreferences::slot_setMapExitsColor()` * `dlgProfilePreferences::setFontSize()` ==> `dlgProfilePreferences::slot_setFontSize()` * `dlgProfilePreferences::setMapInfoBackground()` ==> `dlgProfilePreferences::slot_setMapInfoBgColor()` * `dlgProfilePreferences::setRoomBorderColor()` ==> `dlgProfilePreferences::slot_setMapRoomBorderColor()` * `dlgProfilePreferences::slot_script_selected(...)` ==> `dlgProfilePreferences::slot_scriptSelected(...)` * `dlgProfilePreferences::slot_theme_selected(...)` ==> `dlgProfilePreferences::slot_themeSelected(...)` * `dlgRoomSymbol::colorRejected()` ==> `dlgRoomSymbol::slot_colorRejected()` * `dlgRoomSymbol::colorSelected(...)` ==> `dlgRoomSymbol::slot_colorSelected(...)` * `dlgRoomSymbol::currentColorChanged(...)` ==> `dlgRoomSymbol::slot_currentColorChanged(...)` * `dlgRoomSymbol::openColorSelector()` ==> `dlgRoomSymbol::slot_openColorSelector()` * `dlgRoomSymbol::resetColor()` ==> `dlgRoomSymbol::slot_resetColors()` * `dlgRoomSymbol::updatePreview()` ==> `dlgRoomSymbol::slot_updatePreview()` * `dlgTriggerEditor::slot_show_vars()` ==> `dlgTriggerEditor::slot_showVariables()` * `dlgTriggerEditor::slot_var_changed(...)` ==> `dlgTriggerEditor::slot_variableChanged(...)` * `dlgTriggerEditor::slot_var_selected(...)` ==> `dlgTriggerEditor::slot_variableSelected(...)` * `mudlet::slot_check_manual_update()` ==> `mudlet::slot_manualUpdateCheck()` * `mudlet::slot_close_current_profile()` ==> `mudlet::slot_closeCurrentProfile()` * `mudlet::slot_close_profile_requested(...)` ==> `mudlet::slot_closeProfileRequested(...)` * `mudlet::slot_connection_dlg_finished(...)` ==> `mudlet::slot_connectionDialogueFinished(...)` * `mudlet::slot_module_manager()` ==> `mudlet::slot_moduleManager()` * `mudlet::slot_mudlet_discord()` ==> `mudlet::slot_mudletDiscord()` * `mudlet::slot_multi_view(...)` ==> `mudlet::slot_multiView(const bool state)` * `mudlet::slot_package_manager()` ==> `mudlet::slot_packageManager()` * `mudlet::slot_package_exporter()` ==> `mudlet::slot_packageExporter()` * `mudlet::slot_timer_fires()` ==> `mudlet::slot_timerFires()` * `mudlet::slot_toggle_multi_view()` ==> `mudlet::slot_toggleMultiView()` Also the names for these in particular have been changed to make more sense: * `dlgProfilePreferences::slot_chooseProfilesChanged()` ==> `dlgProfilePreferences::slot_chosenProfilesChanged()` * `dlgProfilePreferences::slot_editor_tab_selected(...)` ==> `dlgProfilePreferences::slot_tabChanged(...)` * `dlgProfilePreferences::slot_passwords_location_changed(...)` ==> `dlgProfilePreferences::slot_passwordStorageLocationChanged(...)` * `dlgProfilePreferences::slot_save_and_exit()` ==> `dlgProfilePreferences::slot_saveAndClose()` * `dlgTriggerEditor::slot_toggleHiddenVar(...)` ==> `dlgTriggerEditor::slot_hideVariable(...)` - changed to distinguish it from `slot_toggleHiddenVariables(...)` * `dlgTriggerEditor::slot_item_selected_save(...)` ==> `dlgTriggerEditor::slot_saveSelectedItem(...)` * `mudlet::slot_discord()` ==> `mudlet::slot_profileDiscord()` - changed to distinguish it from `slot_mudletDiscord()` Not currently used and commented out: * `dlgProfilePreferences::setCommandLineFont()` ==> `dlgProfilePreferences::slot_setCommandLineFont()` Signed-off-by: Stephen Lyons <slysven@virginmedia.com>
2022-08-29 14:27:13 +02:00
&dlgConnectionProfiles::slot_setCustomColor);
}
menu.exec(globalPos);
}
Infrastructure: tidy up naming of SLOT methods and their usage - Part 5 (last) (#6266) When Qt's slot/signal system is used to invoke a method there is some overhead - so it makes sense to ensure developers can spot all such methods (functions). We have tried to do this with a `slot_` prefix to the methods we create but it has not been applied uniformly. This PR is intended to conclude helping with this by more rigorously doing so - the names changed herein should all follow a `slot_`*camelCaseMethodName* style. In addition some methods were detected that are not currently used or which are not actually used via the signal/slot system, these have been commented out or have had the prefix removed and the declaration in the relevant header file moved as appropriate. There were two similar sets of (not prefixed with `slot_`) slots in the Profile preferences class that adjusted the colour settings separately for the main console and the mapper the latter had the same names but with a `2` suffix. To made it more clear I have changed them to include `Map` in their names instead. Also, the NON-slot method: `(void) dlgProfilePreferences::setColor(QPushButton*, QColor&, bool)` has been renamed to: `(void) dlgProfilePreferences::setButtonAndProfileColor(QPushButton*, QColor&, bool)` so that it is clearly distinguishable from built in Qt methods that are also called `setColor` - though which do have different signatures! For reference the changes made are: * `TConsole::slot_stop_all_triggers(...)` ==> `TConsole::slot_stopAllItems(...)` * `dlgConnectionProfiles::slot_copy_profile()` ==> `dlgConnectionProfiles::slot_copyProfile()` * `dlgConnectionProfiles::slot_copy_profilesettings_only()` ==> `dlgConnectionProfiles::slot_copyOnlySettingsOfProfile()` * `dlgConnectionProfiles::slot_deleteprofile_check(...)` ==> `dlgConnectionProfiles::slot_deleteProfileCheck(...)` * `dlgConnectionProfiles::slot_password_deleted(...)` ==> `dlgConnectionProfiles::slot_passwordDeleted(...)` * `dlgConnectionProfiles::slot_password_saved(...) ==> `dlgConnectionProfiles::slot_passwordSaved(...)` * `dlgConnectionProfiles::slot_profile_menu(...)` ==> `dlgConnectionProfiles::slot_profileContextMenu(...)` * `dlgConnectionProfiles::slot_reset_custom_icon()` ==> `dlgConnectionProfiles::slot_resetCustomIcon()` * `dlgConnectionProfiles::slot_set_custom_icon()` ==> `dlgConnectionProfiles::slot_setCustomIcon()` * `dlgConnectionProfiles::slot_set_custom_color()` ==> `dlgConnectionProfiles::slot_setCustomColor()` * `dlgConnectionProfiles::slot_update_autologin(...)` ==> `dlgConnectionProfiles::slot_updateAutoConnect(...)` * `dlgConnectionProfiles::slot_update_autoreconnect(...)` ==> `dlgConnectionProfiles::slot_updateAutoReconnect(...)` * `dlgConnectionProfiles::slot_update_description()` ==> `dlgConnectionProfiles::slot_updateDescription()` * `dlgConnectionProfiles::slot_update_discord_optin(...)` ==> `dlgConnectionProfiles::slot_updateDiscordOptIn(...)` * `dlgProfilePreferences::copyMap()` ==> `dlgProfilePreferences::slot_copyMap()` * `dlgProfilePreferences::downloadMap()` ==> `dlgProfilePreferences::slot_downloadMap()` * `dlgProfilePreferences::hideActionLabel()` ==> `dlgProfilePreferences::slot_hideActionLabel()` * `dlgProfilePreferences::loadMap()` ==> `dlgProfilePreferences::slot_loadMap()` * `dlgProfilePreferences::resetColors()` ==> `dlgProfilePreferences::slot_resetColors()` * `dlgProfilePreferences::resetColors2()` ==> `dlgProfilePreferences::slot_resetMapColors()` * `dlgProfilePreferences::saveMap()` ==> `dlgProfilePreferences::slot_saveMap()` * `dlgProfilePreferences::setBgColor()` ==> `dlgProfilePreferences::slot_setBgColor()` * `dlgProfilePreferences::setBgColor2()` ==> `dlgProfilePreferences::slot_setMapBgColor()` * `dlgProfilePreferences::setColorBlack()` ==> `dlgProfilePreferences::slot_setColorBlack()` * `dlgProfilePreferences::setColorBlack2()` ==> `dlgProfilePreferences::slot_setMapColorBlack()` * `dlgProfilePreferences::setColorBlue()` ==> `dlgProfilePreferences::slot_setColorBlue()` * `dlgProfilePreferences::setColorCyan()` ==> `dlgProfilePreferences::slot_setColorCyan()` * `dlgProfilePreferences::setColorBlue2()` ==> `dlgProfilePreferences::slot_setMapColorBlue()` * `dlgProfilePreferences::setColorCyan2()` ==> `dlgProfilePreferences::slot_setMapColorCyan()` * `dlgProfilePreferences::setColorGreen()` ==> `dlgProfilePreferences::slot_setColorGreen()` * `dlgProfilePreferences::setColorGreen2()` ==> `dlgProfilePreferences::slot_setMapColorGreen()` * `dlgProfilePreferences::setColorLightBlack()` ==> `dlgProfilePreferences::slot_setColorLightBlack()` * `dlgProfilePreferences::setColorLightBlack2()` ==> `dlgProfilePreferences::slot_setMapColorLightBlack()` * `dlgProfilePreferences::setColorLightBlue()` ==> `dlgProfilePreferences::slot_setColorLightBlue()` * `dlgProfilePreferences::setColorLightBlue2()` ==> `dlgProfilePreferences::slot_setMapColorLightBlue()` * `dlgProfilePreferences::setColorLightCyan()` ==> `dlgProfilePreferences::slot_setColorLightCyan()` * `dlgProfilePreferences::setColorLightCyan2()` ==> `dlgProfilePreferences::slot_setMapColorLightCyan()` * `dlgProfilePreferences::setColorLightGreen()` ==> `dlgProfilePreferences::slot_setColorLightGreen()` * `dlgProfilePreferences::setColorLightGreen2()` ==> `dlgProfilePreferences::slot_setMapColorLightGreen()` * `dlgProfilePreferences::setColorLightMagenta()` ==> `dlgProfilePreferences::slot_setColorLightMagenta()` * `dlgProfilePreferences::setColorLightMagenta2()` ==> `dlgProfilePreferences::slot_setMapColorLightMagenta()` * `dlgProfilePreferences::setColorLightRed()` ==> `dlgProfilePreferences::slot_setColorLightRed()` * `dlgProfilePreferences::setColorLightRed2()` ==> `dlgProfilePreferences::slot_setMapColorLightRed()` * `dlgProfilePreferences::setColorLightWhite()` ==> `dlgProfilePreferences::slot_setColorLightWhite()` * `dlgProfilePreferences::setColorLightWhite2()` ==> `dlgProfilePreferences::slot_setMapColorLightWhite()` * `dlgProfilePreferences::setColorLightYellow()` ==> `dlgProfilePreferences::slot_setColorLightYellow()` * `dlgProfilePreferences::setColorLightYellow2()` ==> `dlgProfilePreferences::slot_setMapColorLightYellow()` * `dlgProfilePreferences::setColorMagenta()` ==> `dlgProfilePreferences::slot_setColorMagenta()` * `dlgProfilePreferences::setColorMagenta2()` ==> `dlgProfilePreferences::slot_setMapColorMagenta()` * `dlgProfilePreferences::setColorRed2()` ==> `dlgProfilePreferences::slot_setMapColorRed()` * `dlgProfilePreferences::setColorRed()` ==> `dlgProfilePreferences::slot_setColorRed()` * `dlgProfilePreferences::setColorWhite()` ==> `dlgProfilePreferences::slot_setColorWhite()` * `dlgProfilePreferences::setColorWhite2()` ==> `dlgProfilePreferences::slot_setMapColorWhite()` * `dlgProfilePreferences::setColorYellow()` ==> `dlgProfilePreferences::slot_setColorYellow()` * `dlgProfilePreferences::setColorYellow2()` ==> `dlgProfilePreferences::slot_setMapColorYellow()` * `dlgProfilePreferences::setCommandBgColor()` ==> `dlgProfilePreferences::slot_setCommandBgColor()` * `dlgProfilePreferences::setCommandFgColor()` ==> `dlgProfilePreferences::slot_setCommandFgColor()` * `dlgProfilePreferences::setCommandLineBgColor()` ==> `dlgProfilePreferences::slot_setCommandLineBgColor()` * `dlgProfilePreferences::setCommandLineFgColor()` ==> `dlgProfilePreferences::slot_setCommandLineFgColor()` * `dlgProfilePreferences::setDisplayFont()` ==> `dlgProfilePreferences::slot_setDisplayFont()` * `dlgProfilePreferences::setFgColor()` ==> `dlgProfilePreferences::slot_setFgColor()` * `dlgProfilePreferences::setFgColor2()` ==> `dlgProfilePreferences::slot_setMapExitsColor()` * `dlgProfilePreferences::setFontSize()` ==> `dlgProfilePreferences::slot_setFontSize()` * `dlgProfilePreferences::setMapInfoBackground()` ==> `dlgProfilePreferences::slot_setMapInfoBgColor()` * `dlgProfilePreferences::setRoomBorderColor()` ==> `dlgProfilePreferences::slot_setMapRoomBorderColor()` * `dlgProfilePreferences::slot_script_selected(...)` ==> `dlgProfilePreferences::slot_scriptSelected(...)` * `dlgProfilePreferences::slot_theme_selected(...)` ==> `dlgProfilePreferences::slot_themeSelected(...)` * `dlgRoomSymbol::colorRejected()` ==> `dlgRoomSymbol::slot_colorRejected()` * `dlgRoomSymbol::colorSelected(...)` ==> `dlgRoomSymbol::slot_colorSelected(...)` * `dlgRoomSymbol::currentColorChanged(...)` ==> `dlgRoomSymbol::slot_currentColorChanged(...)` * `dlgRoomSymbol::openColorSelector()` ==> `dlgRoomSymbol::slot_openColorSelector()` * `dlgRoomSymbol::resetColor()` ==> `dlgRoomSymbol::slot_resetColors()` * `dlgRoomSymbol::updatePreview()` ==> `dlgRoomSymbol::slot_updatePreview()` * `dlgTriggerEditor::slot_show_vars()` ==> `dlgTriggerEditor::slot_showVariables()` * `dlgTriggerEditor::slot_var_changed(...)` ==> `dlgTriggerEditor::slot_variableChanged(...)` * `dlgTriggerEditor::slot_var_selected(...)` ==> `dlgTriggerEditor::slot_variableSelected(...)` * `mudlet::slot_check_manual_update()` ==> `mudlet::slot_manualUpdateCheck()` * `mudlet::slot_close_current_profile()` ==> `mudlet::slot_closeCurrentProfile()` * `mudlet::slot_close_profile_requested(...)` ==> `mudlet::slot_closeProfileRequested(...)` * `mudlet::slot_connection_dlg_finished(...)` ==> `mudlet::slot_connectionDialogueFinished(...)` * `mudlet::slot_module_manager()` ==> `mudlet::slot_moduleManager()` * `mudlet::slot_mudlet_discord()` ==> `mudlet::slot_mudletDiscord()` * `mudlet::slot_multi_view(...)` ==> `mudlet::slot_multiView(const bool state)` * `mudlet::slot_package_manager()` ==> `mudlet::slot_packageManager()` * `mudlet::slot_package_exporter()` ==> `mudlet::slot_packageExporter()` * `mudlet::slot_timer_fires()` ==> `mudlet::slot_timerFires()` * `mudlet::slot_toggle_multi_view()` ==> `mudlet::slot_toggleMultiView()` Also the names for these in particular have been changed to make more sense: * `dlgProfilePreferences::slot_chooseProfilesChanged()` ==> `dlgProfilePreferences::slot_chosenProfilesChanged()` * `dlgProfilePreferences::slot_editor_tab_selected(...)` ==> `dlgProfilePreferences::slot_tabChanged(...)` * `dlgProfilePreferences::slot_passwords_location_changed(...)` ==> `dlgProfilePreferences::slot_passwordStorageLocationChanged(...)` * `dlgProfilePreferences::slot_save_and_exit()` ==> `dlgProfilePreferences::slot_saveAndClose()` * `dlgTriggerEditor::slot_toggleHiddenVar(...)` ==> `dlgTriggerEditor::slot_hideVariable(...)` - changed to distinguish it from `slot_toggleHiddenVariables(...)` * `dlgTriggerEditor::slot_item_selected_save(...)` ==> `dlgTriggerEditor::slot_saveSelectedItem(...)` * `mudlet::slot_discord()` ==> `mudlet::slot_profileDiscord()` - changed to distinguish it from `slot_mudletDiscord()` Not currently used and commented out: * `dlgProfilePreferences::setCommandLineFont()` ==> `dlgProfilePreferences::slot_setCommandLineFont()` Signed-off-by: Stephen Lyons <slysven@virginmedia.com>
2022-08-29 14:27:13 +02:00
void dlgConnectionProfiles::slot_setCustomIcon()
{
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
const auto profileName = selectedProfileName();
if (profileName.isEmpty()) {
return;
}
QSettings& settings = *mudlet::getQSettings();
QString lastDir = settings.value("lastFileDialogLocation", QDir::homePath()).toString();
const QString imageLocation = QFileDialog::getOpenFileName(this, tr("Select custom image for profile (should be 120x30)"), lastDir, tr("Images (%1)").arg(qsl("*.png *.gif *.jpg")));
if (imageLocation.isEmpty()) {
return;
}
lastDir = QFileInfo(imageLocation).absolutePath();
settings.setValue("lastFileDialogLocation", lastDir);
const bool success = mudlet::self()->setProfileIcon(profileName, imageLocation).first;
if (!success) {
return;
}
auto icon = QIcon(QPixmap(imageLocation).scaled(QSize(120, 30), Qt::IgnoreAspectRatio, Qt::SmoothTransformation).copy());
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
// the file dialog ran a nested event loop, so the current item may have moved
setIconOfListedProfile(profileName, icon);
}
Infrastructure: tidy up naming of SLOT methods and their usage - Part 5 (last) (#6266) When Qt's slot/signal system is used to invoke a method there is some overhead - so it makes sense to ensure developers can spot all such methods (functions). We have tried to do this with a `slot_` prefix to the methods we create but it has not been applied uniformly. This PR is intended to conclude helping with this by more rigorously doing so - the names changed herein should all follow a `slot_`*camelCaseMethodName* style. In addition some methods were detected that are not currently used or which are not actually used via the signal/slot system, these have been commented out or have had the prefix removed and the declaration in the relevant header file moved as appropriate. There were two similar sets of (not prefixed with `slot_`) slots in the Profile preferences class that adjusted the colour settings separately for the main console and the mapper the latter had the same names but with a `2` suffix. To made it more clear I have changed them to include `Map` in their names instead. Also, the NON-slot method: `(void) dlgProfilePreferences::setColor(QPushButton*, QColor&, bool)` has been renamed to: `(void) dlgProfilePreferences::setButtonAndProfileColor(QPushButton*, QColor&, bool)` so that it is clearly distinguishable from built in Qt methods that are also called `setColor` - though which do have different signatures! For reference the changes made are: * `TConsole::slot_stop_all_triggers(...)` ==> `TConsole::slot_stopAllItems(...)` * `dlgConnectionProfiles::slot_copy_profile()` ==> `dlgConnectionProfiles::slot_copyProfile()` * `dlgConnectionProfiles::slot_copy_profilesettings_only()` ==> `dlgConnectionProfiles::slot_copyOnlySettingsOfProfile()` * `dlgConnectionProfiles::slot_deleteprofile_check(...)` ==> `dlgConnectionProfiles::slot_deleteProfileCheck(...)` * `dlgConnectionProfiles::slot_password_deleted(...)` ==> `dlgConnectionProfiles::slot_passwordDeleted(...)` * `dlgConnectionProfiles::slot_password_saved(...) ==> `dlgConnectionProfiles::slot_passwordSaved(...)` * `dlgConnectionProfiles::slot_profile_menu(...)` ==> `dlgConnectionProfiles::slot_profileContextMenu(...)` * `dlgConnectionProfiles::slot_reset_custom_icon()` ==> `dlgConnectionProfiles::slot_resetCustomIcon()` * `dlgConnectionProfiles::slot_set_custom_icon()` ==> `dlgConnectionProfiles::slot_setCustomIcon()` * `dlgConnectionProfiles::slot_set_custom_color()` ==> `dlgConnectionProfiles::slot_setCustomColor()` * `dlgConnectionProfiles::slot_update_autologin(...)` ==> `dlgConnectionProfiles::slot_updateAutoConnect(...)` * `dlgConnectionProfiles::slot_update_autoreconnect(...)` ==> `dlgConnectionProfiles::slot_updateAutoReconnect(...)` * `dlgConnectionProfiles::slot_update_description()` ==> `dlgConnectionProfiles::slot_updateDescription()` * `dlgConnectionProfiles::slot_update_discord_optin(...)` ==> `dlgConnectionProfiles::slot_updateDiscordOptIn(...)` * `dlgProfilePreferences::copyMap()` ==> `dlgProfilePreferences::slot_copyMap()` * `dlgProfilePreferences::downloadMap()` ==> `dlgProfilePreferences::slot_downloadMap()` * `dlgProfilePreferences::hideActionLabel()` ==> `dlgProfilePreferences::slot_hideActionLabel()` * `dlgProfilePreferences::loadMap()` ==> `dlgProfilePreferences::slot_loadMap()` * `dlgProfilePreferences::resetColors()` ==> `dlgProfilePreferences::slot_resetColors()` * `dlgProfilePreferences::resetColors2()` ==> `dlgProfilePreferences::slot_resetMapColors()` * `dlgProfilePreferences::saveMap()` ==> `dlgProfilePreferences::slot_saveMap()` * `dlgProfilePreferences::setBgColor()` ==> `dlgProfilePreferences::slot_setBgColor()` * `dlgProfilePreferences::setBgColor2()` ==> `dlgProfilePreferences::slot_setMapBgColor()` * `dlgProfilePreferences::setColorBlack()` ==> `dlgProfilePreferences::slot_setColorBlack()` * `dlgProfilePreferences::setColorBlack2()` ==> `dlgProfilePreferences::slot_setMapColorBlack()` * `dlgProfilePreferences::setColorBlue()` ==> `dlgProfilePreferences::slot_setColorBlue()` * `dlgProfilePreferences::setColorCyan()` ==> `dlgProfilePreferences::slot_setColorCyan()` * `dlgProfilePreferences::setColorBlue2()` ==> `dlgProfilePreferences::slot_setMapColorBlue()` * `dlgProfilePreferences::setColorCyan2()` ==> `dlgProfilePreferences::slot_setMapColorCyan()` * `dlgProfilePreferences::setColorGreen()` ==> `dlgProfilePreferences::slot_setColorGreen()` * `dlgProfilePreferences::setColorGreen2()` ==> `dlgProfilePreferences::slot_setMapColorGreen()` * `dlgProfilePreferences::setColorLightBlack()` ==> `dlgProfilePreferences::slot_setColorLightBlack()` * `dlgProfilePreferences::setColorLightBlack2()` ==> `dlgProfilePreferences::slot_setMapColorLightBlack()` * `dlgProfilePreferences::setColorLightBlue()` ==> `dlgProfilePreferences::slot_setColorLightBlue()` * `dlgProfilePreferences::setColorLightBlue2()` ==> `dlgProfilePreferences::slot_setMapColorLightBlue()` * `dlgProfilePreferences::setColorLightCyan()` ==> `dlgProfilePreferences::slot_setColorLightCyan()` * `dlgProfilePreferences::setColorLightCyan2()` ==> `dlgProfilePreferences::slot_setMapColorLightCyan()` * `dlgProfilePreferences::setColorLightGreen()` ==> `dlgProfilePreferences::slot_setColorLightGreen()` * `dlgProfilePreferences::setColorLightGreen2()` ==> `dlgProfilePreferences::slot_setMapColorLightGreen()` * `dlgProfilePreferences::setColorLightMagenta()` ==> `dlgProfilePreferences::slot_setColorLightMagenta()` * `dlgProfilePreferences::setColorLightMagenta2()` ==> `dlgProfilePreferences::slot_setMapColorLightMagenta()` * `dlgProfilePreferences::setColorLightRed()` ==> `dlgProfilePreferences::slot_setColorLightRed()` * `dlgProfilePreferences::setColorLightRed2()` ==> `dlgProfilePreferences::slot_setMapColorLightRed()` * `dlgProfilePreferences::setColorLightWhite()` ==> `dlgProfilePreferences::slot_setColorLightWhite()` * `dlgProfilePreferences::setColorLightWhite2()` ==> `dlgProfilePreferences::slot_setMapColorLightWhite()` * `dlgProfilePreferences::setColorLightYellow()` ==> `dlgProfilePreferences::slot_setColorLightYellow()` * `dlgProfilePreferences::setColorLightYellow2()` ==> `dlgProfilePreferences::slot_setMapColorLightYellow()` * `dlgProfilePreferences::setColorMagenta()` ==> `dlgProfilePreferences::slot_setColorMagenta()` * `dlgProfilePreferences::setColorMagenta2()` ==> `dlgProfilePreferences::slot_setMapColorMagenta()` * `dlgProfilePreferences::setColorRed2()` ==> `dlgProfilePreferences::slot_setMapColorRed()` * `dlgProfilePreferences::setColorRed()` ==> `dlgProfilePreferences::slot_setColorRed()` * `dlgProfilePreferences::setColorWhite()` ==> `dlgProfilePreferences::slot_setColorWhite()` * `dlgProfilePreferences::setColorWhite2()` ==> `dlgProfilePreferences::slot_setMapColorWhite()` * `dlgProfilePreferences::setColorYellow()` ==> `dlgProfilePreferences::slot_setColorYellow()` * `dlgProfilePreferences::setColorYellow2()` ==> `dlgProfilePreferences::slot_setMapColorYellow()` * `dlgProfilePreferences::setCommandBgColor()` ==> `dlgProfilePreferences::slot_setCommandBgColor()` * `dlgProfilePreferences::setCommandFgColor()` ==> `dlgProfilePreferences::slot_setCommandFgColor()` * `dlgProfilePreferences::setCommandLineBgColor()` ==> `dlgProfilePreferences::slot_setCommandLineBgColor()` * `dlgProfilePreferences::setCommandLineFgColor()` ==> `dlgProfilePreferences::slot_setCommandLineFgColor()` * `dlgProfilePreferences::setDisplayFont()` ==> `dlgProfilePreferences::slot_setDisplayFont()` * `dlgProfilePreferences::setFgColor()` ==> `dlgProfilePreferences::slot_setFgColor()` * `dlgProfilePreferences::setFgColor2()` ==> `dlgProfilePreferences::slot_setMapExitsColor()` * `dlgProfilePreferences::setFontSize()` ==> `dlgProfilePreferences::slot_setFontSize()` * `dlgProfilePreferences::setMapInfoBackground()` ==> `dlgProfilePreferences::slot_setMapInfoBgColor()` * `dlgProfilePreferences::setRoomBorderColor()` ==> `dlgProfilePreferences::slot_setMapRoomBorderColor()` * `dlgProfilePreferences::slot_script_selected(...)` ==> `dlgProfilePreferences::slot_scriptSelected(...)` * `dlgProfilePreferences::slot_theme_selected(...)` ==> `dlgProfilePreferences::slot_themeSelected(...)` * `dlgRoomSymbol::colorRejected()` ==> `dlgRoomSymbol::slot_colorRejected()` * `dlgRoomSymbol::colorSelected(...)` ==> `dlgRoomSymbol::slot_colorSelected(...)` * `dlgRoomSymbol::currentColorChanged(...)` ==> `dlgRoomSymbol::slot_currentColorChanged(...)` * `dlgRoomSymbol::openColorSelector()` ==> `dlgRoomSymbol::slot_openColorSelector()` * `dlgRoomSymbol::resetColor()` ==> `dlgRoomSymbol::slot_resetColors()` * `dlgRoomSymbol::updatePreview()` ==> `dlgRoomSymbol::slot_updatePreview()` * `dlgTriggerEditor::slot_show_vars()` ==> `dlgTriggerEditor::slot_showVariables()` * `dlgTriggerEditor::slot_var_changed(...)` ==> `dlgTriggerEditor::slot_variableChanged(...)` * `dlgTriggerEditor::slot_var_selected(...)` ==> `dlgTriggerEditor::slot_variableSelected(...)` * `mudlet::slot_check_manual_update()` ==> `mudlet::slot_manualUpdateCheck()` * `mudlet::slot_close_current_profile()` ==> `mudlet::slot_closeCurrentProfile()` * `mudlet::slot_close_profile_requested(...)` ==> `mudlet::slot_closeProfileRequested(...)` * `mudlet::slot_connection_dlg_finished(...)` ==> `mudlet::slot_connectionDialogueFinished(...)` * `mudlet::slot_module_manager()` ==> `mudlet::slot_moduleManager()` * `mudlet::slot_mudlet_discord()` ==> `mudlet::slot_mudletDiscord()` * `mudlet::slot_multi_view(...)` ==> `mudlet::slot_multiView(const bool state)` * `mudlet::slot_package_manager()` ==> `mudlet::slot_packageManager()` * `mudlet::slot_package_exporter()` ==> `mudlet::slot_packageExporter()` * `mudlet::slot_timer_fires()` ==> `mudlet::slot_timerFires()` * `mudlet::slot_toggle_multi_view()` ==> `mudlet::slot_toggleMultiView()` Also the names for these in particular have been changed to make more sense: * `dlgProfilePreferences::slot_chooseProfilesChanged()` ==> `dlgProfilePreferences::slot_chosenProfilesChanged()` * `dlgProfilePreferences::slot_editor_tab_selected(...)` ==> `dlgProfilePreferences::slot_tabChanged(...)` * `dlgProfilePreferences::slot_passwords_location_changed(...)` ==> `dlgProfilePreferences::slot_passwordStorageLocationChanged(...)` * `dlgProfilePreferences::slot_save_and_exit()` ==> `dlgProfilePreferences::slot_saveAndClose()` * `dlgTriggerEditor::slot_toggleHiddenVar(...)` ==> `dlgTriggerEditor::slot_hideVariable(...)` - changed to distinguish it from `slot_toggleHiddenVariables(...)` * `dlgTriggerEditor::slot_item_selected_save(...)` ==> `dlgTriggerEditor::slot_saveSelectedItem(...)` * `mudlet::slot_discord()` ==> `mudlet::slot_profileDiscord()` - changed to distinguish it from `slot_mudletDiscord()` Not currently used and commented out: * `dlgProfilePreferences::setCommandLineFont()` ==> `dlgProfilePreferences::slot_setCommandLineFont()` Signed-off-by: Stephen Lyons <slysven@virginmedia.com>
2022-08-29 14:27:13 +02:00
void dlgConnectionProfiles::slot_setCustomColor()
{
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
const auto profileName = selectedProfileName();
if (profileName.isEmpty()) {
return;
}
QColor color = QColorDialog::getColor(getCustomColor(profileName).value_or(QColor(255, 255, 255)));
if (color.isValid()) {
auto profileColorPath = mudlet::getMudletPath(enums::profileDataItemPath, profileName, qsl("profilecolor"));
QSaveFile file(profileColorPath);
if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
qWarning() << "dlgConnectionProfiles: failed to open profile color file for writing:" << file.errorString();
return;
}
auto colorName = color.name();
file.write(colorName.toUtf8(), colorName.length());
if (!file.commit()) {
qDebug() << "dlgConnectionProfiles::slot_setCustomColor: error saving custom icon color: " << file.errorString();
}
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
setIconOfListedProfile(profileName, customIcon(profileName, {color}));
}
}
Infrastructure: tidy up naming of SLOT methods and their usage - Part 5 (last) (#6266) When Qt's slot/signal system is used to invoke a method there is some overhead - so it makes sense to ensure developers can spot all such methods (functions). We have tried to do this with a `slot_` prefix to the methods we create but it has not been applied uniformly. This PR is intended to conclude helping with this by more rigorously doing so - the names changed herein should all follow a `slot_`*camelCaseMethodName* style. In addition some methods were detected that are not currently used or which are not actually used via the signal/slot system, these have been commented out or have had the prefix removed and the declaration in the relevant header file moved as appropriate. There were two similar sets of (not prefixed with `slot_`) slots in the Profile preferences class that adjusted the colour settings separately for the main console and the mapper the latter had the same names but with a `2` suffix. To made it more clear I have changed them to include `Map` in their names instead. Also, the NON-slot method: `(void) dlgProfilePreferences::setColor(QPushButton*, QColor&, bool)` has been renamed to: `(void) dlgProfilePreferences::setButtonAndProfileColor(QPushButton*, QColor&, bool)` so that it is clearly distinguishable from built in Qt methods that are also called `setColor` - though which do have different signatures! For reference the changes made are: * `TConsole::slot_stop_all_triggers(...)` ==> `TConsole::slot_stopAllItems(...)` * `dlgConnectionProfiles::slot_copy_profile()` ==> `dlgConnectionProfiles::slot_copyProfile()` * `dlgConnectionProfiles::slot_copy_profilesettings_only()` ==> `dlgConnectionProfiles::slot_copyOnlySettingsOfProfile()` * `dlgConnectionProfiles::slot_deleteprofile_check(...)` ==> `dlgConnectionProfiles::slot_deleteProfileCheck(...)` * `dlgConnectionProfiles::slot_password_deleted(...)` ==> `dlgConnectionProfiles::slot_passwordDeleted(...)` * `dlgConnectionProfiles::slot_password_saved(...) ==> `dlgConnectionProfiles::slot_passwordSaved(...)` * `dlgConnectionProfiles::slot_profile_menu(...)` ==> `dlgConnectionProfiles::slot_profileContextMenu(...)` * `dlgConnectionProfiles::slot_reset_custom_icon()` ==> `dlgConnectionProfiles::slot_resetCustomIcon()` * `dlgConnectionProfiles::slot_set_custom_icon()` ==> `dlgConnectionProfiles::slot_setCustomIcon()` * `dlgConnectionProfiles::slot_set_custom_color()` ==> `dlgConnectionProfiles::slot_setCustomColor()` * `dlgConnectionProfiles::slot_update_autologin(...)` ==> `dlgConnectionProfiles::slot_updateAutoConnect(...)` * `dlgConnectionProfiles::slot_update_autoreconnect(...)` ==> `dlgConnectionProfiles::slot_updateAutoReconnect(...)` * `dlgConnectionProfiles::slot_update_description()` ==> `dlgConnectionProfiles::slot_updateDescription()` * `dlgConnectionProfiles::slot_update_discord_optin(...)` ==> `dlgConnectionProfiles::slot_updateDiscordOptIn(...)` * `dlgProfilePreferences::copyMap()` ==> `dlgProfilePreferences::slot_copyMap()` * `dlgProfilePreferences::downloadMap()` ==> `dlgProfilePreferences::slot_downloadMap()` * `dlgProfilePreferences::hideActionLabel()` ==> `dlgProfilePreferences::slot_hideActionLabel()` * `dlgProfilePreferences::loadMap()` ==> `dlgProfilePreferences::slot_loadMap()` * `dlgProfilePreferences::resetColors()` ==> `dlgProfilePreferences::slot_resetColors()` * `dlgProfilePreferences::resetColors2()` ==> `dlgProfilePreferences::slot_resetMapColors()` * `dlgProfilePreferences::saveMap()` ==> `dlgProfilePreferences::slot_saveMap()` * `dlgProfilePreferences::setBgColor()` ==> `dlgProfilePreferences::slot_setBgColor()` * `dlgProfilePreferences::setBgColor2()` ==> `dlgProfilePreferences::slot_setMapBgColor()` * `dlgProfilePreferences::setColorBlack()` ==> `dlgProfilePreferences::slot_setColorBlack()` * `dlgProfilePreferences::setColorBlack2()` ==> `dlgProfilePreferences::slot_setMapColorBlack()` * `dlgProfilePreferences::setColorBlue()` ==> `dlgProfilePreferences::slot_setColorBlue()` * `dlgProfilePreferences::setColorCyan()` ==> `dlgProfilePreferences::slot_setColorCyan()` * `dlgProfilePreferences::setColorBlue2()` ==> `dlgProfilePreferences::slot_setMapColorBlue()` * `dlgProfilePreferences::setColorCyan2()` ==> `dlgProfilePreferences::slot_setMapColorCyan()` * `dlgProfilePreferences::setColorGreen()` ==> `dlgProfilePreferences::slot_setColorGreen()` * `dlgProfilePreferences::setColorGreen2()` ==> `dlgProfilePreferences::slot_setMapColorGreen()` * `dlgProfilePreferences::setColorLightBlack()` ==> `dlgProfilePreferences::slot_setColorLightBlack()` * `dlgProfilePreferences::setColorLightBlack2()` ==> `dlgProfilePreferences::slot_setMapColorLightBlack()` * `dlgProfilePreferences::setColorLightBlue()` ==> `dlgProfilePreferences::slot_setColorLightBlue()` * `dlgProfilePreferences::setColorLightBlue2()` ==> `dlgProfilePreferences::slot_setMapColorLightBlue()` * `dlgProfilePreferences::setColorLightCyan()` ==> `dlgProfilePreferences::slot_setColorLightCyan()` * `dlgProfilePreferences::setColorLightCyan2()` ==> `dlgProfilePreferences::slot_setMapColorLightCyan()` * `dlgProfilePreferences::setColorLightGreen()` ==> `dlgProfilePreferences::slot_setColorLightGreen()` * `dlgProfilePreferences::setColorLightGreen2()` ==> `dlgProfilePreferences::slot_setMapColorLightGreen()` * `dlgProfilePreferences::setColorLightMagenta()` ==> `dlgProfilePreferences::slot_setColorLightMagenta()` * `dlgProfilePreferences::setColorLightMagenta2()` ==> `dlgProfilePreferences::slot_setMapColorLightMagenta()` * `dlgProfilePreferences::setColorLightRed()` ==> `dlgProfilePreferences::slot_setColorLightRed()` * `dlgProfilePreferences::setColorLightRed2()` ==> `dlgProfilePreferences::slot_setMapColorLightRed()` * `dlgProfilePreferences::setColorLightWhite()` ==> `dlgProfilePreferences::slot_setColorLightWhite()` * `dlgProfilePreferences::setColorLightWhite2()` ==> `dlgProfilePreferences::slot_setMapColorLightWhite()` * `dlgProfilePreferences::setColorLightYellow()` ==> `dlgProfilePreferences::slot_setColorLightYellow()` * `dlgProfilePreferences::setColorLightYellow2()` ==> `dlgProfilePreferences::slot_setMapColorLightYellow()` * `dlgProfilePreferences::setColorMagenta()` ==> `dlgProfilePreferences::slot_setColorMagenta()` * `dlgProfilePreferences::setColorMagenta2()` ==> `dlgProfilePreferences::slot_setMapColorMagenta()` * `dlgProfilePreferences::setColorRed2()` ==> `dlgProfilePreferences::slot_setMapColorRed()` * `dlgProfilePreferences::setColorRed()` ==> `dlgProfilePreferences::slot_setColorRed()` * `dlgProfilePreferences::setColorWhite()` ==> `dlgProfilePreferences::slot_setColorWhite()` * `dlgProfilePreferences::setColorWhite2()` ==> `dlgProfilePreferences::slot_setMapColorWhite()` * `dlgProfilePreferences::setColorYellow()` ==> `dlgProfilePreferences::slot_setColorYellow()` * `dlgProfilePreferences::setColorYellow2()` ==> `dlgProfilePreferences::slot_setMapColorYellow()` * `dlgProfilePreferences::setCommandBgColor()` ==> `dlgProfilePreferences::slot_setCommandBgColor()` * `dlgProfilePreferences::setCommandFgColor()` ==> `dlgProfilePreferences::slot_setCommandFgColor()` * `dlgProfilePreferences::setCommandLineBgColor()` ==> `dlgProfilePreferences::slot_setCommandLineBgColor()` * `dlgProfilePreferences::setCommandLineFgColor()` ==> `dlgProfilePreferences::slot_setCommandLineFgColor()` * `dlgProfilePreferences::setDisplayFont()` ==> `dlgProfilePreferences::slot_setDisplayFont()` * `dlgProfilePreferences::setFgColor()` ==> `dlgProfilePreferences::slot_setFgColor()` * `dlgProfilePreferences::setFgColor2()` ==> `dlgProfilePreferences::slot_setMapExitsColor()` * `dlgProfilePreferences::setFontSize()` ==> `dlgProfilePreferences::slot_setFontSize()` * `dlgProfilePreferences::setMapInfoBackground()` ==> `dlgProfilePreferences::slot_setMapInfoBgColor()` * `dlgProfilePreferences::setRoomBorderColor()` ==> `dlgProfilePreferences::slot_setMapRoomBorderColor()` * `dlgProfilePreferences::slot_script_selected(...)` ==> `dlgProfilePreferences::slot_scriptSelected(...)` * `dlgProfilePreferences::slot_theme_selected(...)` ==> `dlgProfilePreferences::slot_themeSelected(...)` * `dlgRoomSymbol::colorRejected()` ==> `dlgRoomSymbol::slot_colorRejected()` * `dlgRoomSymbol::colorSelected(...)` ==> `dlgRoomSymbol::slot_colorSelected(...)` * `dlgRoomSymbol::currentColorChanged(...)` ==> `dlgRoomSymbol::slot_currentColorChanged(...)` * `dlgRoomSymbol::openColorSelector()` ==> `dlgRoomSymbol::slot_openColorSelector()` * `dlgRoomSymbol::resetColor()` ==> `dlgRoomSymbol::slot_resetColors()` * `dlgRoomSymbol::updatePreview()` ==> `dlgRoomSymbol::slot_updatePreview()` * `dlgTriggerEditor::slot_show_vars()` ==> `dlgTriggerEditor::slot_showVariables()` * `dlgTriggerEditor::slot_var_changed(...)` ==> `dlgTriggerEditor::slot_variableChanged(...)` * `dlgTriggerEditor::slot_var_selected(...)` ==> `dlgTriggerEditor::slot_variableSelected(...)` * `mudlet::slot_check_manual_update()` ==> `mudlet::slot_manualUpdateCheck()` * `mudlet::slot_close_current_profile()` ==> `mudlet::slot_closeCurrentProfile()` * `mudlet::slot_close_profile_requested(...)` ==> `mudlet::slot_closeProfileRequested(...)` * `mudlet::slot_connection_dlg_finished(...)` ==> `mudlet::slot_connectionDialogueFinished(...)` * `mudlet::slot_module_manager()` ==> `mudlet::slot_moduleManager()` * `mudlet::slot_mudlet_discord()` ==> `mudlet::slot_mudletDiscord()` * `mudlet::slot_multi_view(...)` ==> `mudlet::slot_multiView(const bool state)` * `mudlet::slot_package_manager()` ==> `mudlet::slot_packageManager()` * `mudlet::slot_package_exporter()` ==> `mudlet::slot_packageExporter()` * `mudlet::slot_timer_fires()` ==> `mudlet::slot_timerFires()` * `mudlet::slot_toggle_multi_view()` ==> `mudlet::slot_toggleMultiView()` Also the names for these in particular have been changed to make more sense: * `dlgProfilePreferences::slot_chooseProfilesChanged()` ==> `dlgProfilePreferences::slot_chosenProfilesChanged()` * `dlgProfilePreferences::slot_editor_tab_selected(...)` ==> `dlgProfilePreferences::slot_tabChanged(...)` * `dlgProfilePreferences::slot_passwords_location_changed(...)` ==> `dlgProfilePreferences::slot_passwordStorageLocationChanged(...)` * `dlgProfilePreferences::slot_save_and_exit()` ==> `dlgProfilePreferences::slot_saveAndClose()` * `dlgTriggerEditor::slot_toggleHiddenVar(...)` ==> `dlgTriggerEditor::slot_hideVariable(...)` - changed to distinguish it from `slot_toggleHiddenVariables(...)` * `dlgTriggerEditor::slot_item_selected_save(...)` ==> `dlgTriggerEditor::slot_saveSelectedItem(...)` * `mudlet::slot_discord()` ==> `mudlet::slot_profileDiscord()` - changed to distinguish it from `slot_mudletDiscord()` Not currently used and commented out: * `dlgProfilePreferences::setCommandLineFont()` ==> `dlgProfilePreferences::slot_setCommandLineFont()` Signed-off-by: Stephen Lyons <slysven@virginmedia.com>
2022-08-29 14:27:13 +02:00
void dlgConnectionProfiles::slot_resetCustomIcon()
{
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
const auto profileName = selectedProfileName();
if (profileName.isEmpty()) {
return;
}
const bool success = mudlet::self()->resetProfileIcon(profileName).first;
if (!success) {
return;
}
auto currentRow = listWidget_profiles->currentRow();
fillout_form();
listWidget_profiles->setCurrentRow(currentRow);
}
void dlgConnectionProfiles::slot_cancel()
{
Update: remove Midkemia (and add WoTMUD) to list of predefined MUDs (#321) * Update: remove Midkemia (and add WoTMUD) to list of predefined MUDs The I.R.E. MUD Midkemia-online was shutdown on 2016/09/07 and it is not helpful to continue to offer it as a predefined MUD in the connection dialog. This commit removes it from that list and adds a favourite of mine "WoTMUD" to replace it. The logo is a temporary one that I will replace with a similar one that I need to get clearance to use (and release with a GPL2.1+ licence) from the Wheel of Time MUD sysop with whom I am in contact {Vivienne needs to get approval from the creator Flash who is away for a few days...!} The dlgConnectionProfiles.cpp file has quite a few ASCII strings, as appropriate for a file that will in the future be subject to I18n work I took the opportunity to put QStringLiteral(...) wrappers around the QStrings that are used for non-GUI tasks and tr(...) ones that are. I expect it isn't complete but it goes someway towards dealing with THIS particular file - and it should improve very slightly the generation of QStrings that need to be constructed because it means as much of possible of them is done at compile rather than run-time...! In relation to the above there are points in the code where QStrings are compared to "" to see if they do not have any contents - the isEmpty() method is a better way to do the same (it does not involve a string expression) In (void)dlgConnectionProfiles::slot_update_name( const QString ) there are a couple of variables that use a double underscore prefix - that is NOT A Good Idea: such a prefix is RESERVED in both C and C++. For example see: [what-are-the-rules-about-using-an-underscore-in-a-c-identifier](http://stackoverflow.com/questions/228783/what-are-the-rules-about-using-an-underscore-in-a-c-identifier) also a single prefix might be a bit of a problem with libraries... I have renames the offending things there - but I may have used the single underscore prefix myself in the past. *blush* There are some help messages in this class that use an apostrophe in a word contraction - as a matter of readability this is not considered best practice IMHO so I have converted them to the full words. Following experiences in my previous Pull Request I also took a look at the initialisation and use of some member variables; it became clear that: * (QString) dlgConnectionProfiles::mOrigin * (bool) dlgConnectionProfiles::mEditOK were not used and could be simply deleted and that: * (QString) dlgConnectionProfiles::mUnsavedProfileName * (QString) dlgConnectionProfiles::mCurrentProfileEditName did not need to be member variables and could be local to the method that used them, so in (void)dlgConnectionProfiles::slot_save_name(): * (QString) dlgConnectionProfiles::mCurrentProfileEditName becomes local: (QString) currentProfileEditName and in (void) dlgConnectionProfiles::slot_addProfile(): * (QString) dlgConnectionProfiles::mUnsavedProfileName becomes local: (QString) newName also added initialisers for: * (QStringList) dlgConnectionProfiles::mProfileList * (QPushButton *) dlgConnectionProfiles::connect_button * (QLineEditQPushButton *) dlgConnectionProfiles::delete_profile_lineedit * (QPushButton *) dlgConnectionProfiles::delete_button Signed-off-by: Stephen Lyons <slysven@virginmedia.com> * More: two items missed from prior commit In the previous commit I forgot to take out: * a check for Midkemia in the dlgProfilePreferences class constructor that enabled the download map options; * a similar check in (bool)TMap::restore(QString) that asked if the user wanted to download a map if there was not one found. Signed-off-by: Stephen Lyons <slysven@virginmedia.com> * Tweak: Insert MUD supplied Logo/Icon Received an icon as a .jpg that the MUD operators can allow us to licence as GPL2.1+ which I have converted to a .png file. Signed-off-by: Stephen Lyons <slysven@virginmedia.com>
2016-10-15 09:57:17 +01:00
// QDialog::Rejected is the enum value (= 0) return value for a "cancelled"
// outcome...
QDialog::done(QDialog::Rejected);
2009-01-24 02:50:22 +01:00
}
Infrastructure: tidy up naming of SLOT methods and their usage - Part 5 (last) (#6266) When Qt's slot/signal system is used to invoke a method there is some overhead - so it makes sense to ensure developers can spot all such methods (functions). We have tried to do this with a `slot_` prefix to the methods we create but it has not been applied uniformly. This PR is intended to conclude helping with this by more rigorously doing so - the names changed herein should all follow a `slot_`*camelCaseMethodName* style. In addition some methods were detected that are not currently used or which are not actually used via the signal/slot system, these have been commented out or have had the prefix removed and the declaration in the relevant header file moved as appropriate. There were two similar sets of (not prefixed with `slot_`) slots in the Profile preferences class that adjusted the colour settings separately for the main console and the mapper the latter had the same names but with a `2` suffix. To made it more clear I have changed them to include `Map` in their names instead. Also, the NON-slot method: `(void) dlgProfilePreferences::setColor(QPushButton*, QColor&, bool)` has been renamed to: `(void) dlgProfilePreferences::setButtonAndProfileColor(QPushButton*, QColor&, bool)` so that it is clearly distinguishable from built in Qt methods that are also called `setColor` - though which do have different signatures! For reference the changes made are: * `TConsole::slot_stop_all_triggers(...)` ==> `TConsole::slot_stopAllItems(...)` * `dlgConnectionProfiles::slot_copy_profile()` ==> `dlgConnectionProfiles::slot_copyProfile()` * `dlgConnectionProfiles::slot_copy_profilesettings_only()` ==> `dlgConnectionProfiles::slot_copyOnlySettingsOfProfile()` * `dlgConnectionProfiles::slot_deleteprofile_check(...)` ==> `dlgConnectionProfiles::slot_deleteProfileCheck(...)` * `dlgConnectionProfiles::slot_password_deleted(...)` ==> `dlgConnectionProfiles::slot_passwordDeleted(...)` * `dlgConnectionProfiles::slot_password_saved(...) ==> `dlgConnectionProfiles::slot_passwordSaved(...)` * `dlgConnectionProfiles::slot_profile_menu(...)` ==> `dlgConnectionProfiles::slot_profileContextMenu(...)` * `dlgConnectionProfiles::slot_reset_custom_icon()` ==> `dlgConnectionProfiles::slot_resetCustomIcon()` * `dlgConnectionProfiles::slot_set_custom_icon()` ==> `dlgConnectionProfiles::slot_setCustomIcon()` * `dlgConnectionProfiles::slot_set_custom_color()` ==> `dlgConnectionProfiles::slot_setCustomColor()` * `dlgConnectionProfiles::slot_update_autologin(...)` ==> `dlgConnectionProfiles::slot_updateAutoConnect(...)` * `dlgConnectionProfiles::slot_update_autoreconnect(...)` ==> `dlgConnectionProfiles::slot_updateAutoReconnect(...)` * `dlgConnectionProfiles::slot_update_description()` ==> `dlgConnectionProfiles::slot_updateDescription()` * `dlgConnectionProfiles::slot_update_discord_optin(...)` ==> `dlgConnectionProfiles::slot_updateDiscordOptIn(...)` * `dlgProfilePreferences::copyMap()` ==> `dlgProfilePreferences::slot_copyMap()` * `dlgProfilePreferences::downloadMap()` ==> `dlgProfilePreferences::slot_downloadMap()` * `dlgProfilePreferences::hideActionLabel()` ==> `dlgProfilePreferences::slot_hideActionLabel()` * `dlgProfilePreferences::loadMap()` ==> `dlgProfilePreferences::slot_loadMap()` * `dlgProfilePreferences::resetColors()` ==> `dlgProfilePreferences::slot_resetColors()` * `dlgProfilePreferences::resetColors2()` ==> `dlgProfilePreferences::slot_resetMapColors()` * `dlgProfilePreferences::saveMap()` ==> `dlgProfilePreferences::slot_saveMap()` * `dlgProfilePreferences::setBgColor()` ==> `dlgProfilePreferences::slot_setBgColor()` * `dlgProfilePreferences::setBgColor2()` ==> `dlgProfilePreferences::slot_setMapBgColor()` * `dlgProfilePreferences::setColorBlack()` ==> `dlgProfilePreferences::slot_setColorBlack()` * `dlgProfilePreferences::setColorBlack2()` ==> `dlgProfilePreferences::slot_setMapColorBlack()` * `dlgProfilePreferences::setColorBlue()` ==> `dlgProfilePreferences::slot_setColorBlue()` * `dlgProfilePreferences::setColorCyan()` ==> `dlgProfilePreferences::slot_setColorCyan()` * `dlgProfilePreferences::setColorBlue2()` ==> `dlgProfilePreferences::slot_setMapColorBlue()` * `dlgProfilePreferences::setColorCyan2()` ==> `dlgProfilePreferences::slot_setMapColorCyan()` * `dlgProfilePreferences::setColorGreen()` ==> `dlgProfilePreferences::slot_setColorGreen()` * `dlgProfilePreferences::setColorGreen2()` ==> `dlgProfilePreferences::slot_setMapColorGreen()` * `dlgProfilePreferences::setColorLightBlack()` ==> `dlgProfilePreferences::slot_setColorLightBlack()` * `dlgProfilePreferences::setColorLightBlack2()` ==> `dlgProfilePreferences::slot_setMapColorLightBlack()` * `dlgProfilePreferences::setColorLightBlue()` ==> `dlgProfilePreferences::slot_setColorLightBlue()` * `dlgProfilePreferences::setColorLightBlue2()` ==> `dlgProfilePreferences::slot_setMapColorLightBlue()` * `dlgProfilePreferences::setColorLightCyan()` ==> `dlgProfilePreferences::slot_setColorLightCyan()` * `dlgProfilePreferences::setColorLightCyan2()` ==> `dlgProfilePreferences::slot_setMapColorLightCyan()` * `dlgProfilePreferences::setColorLightGreen()` ==> `dlgProfilePreferences::slot_setColorLightGreen()` * `dlgProfilePreferences::setColorLightGreen2()` ==> `dlgProfilePreferences::slot_setMapColorLightGreen()` * `dlgProfilePreferences::setColorLightMagenta()` ==> `dlgProfilePreferences::slot_setColorLightMagenta()` * `dlgProfilePreferences::setColorLightMagenta2()` ==> `dlgProfilePreferences::slot_setMapColorLightMagenta()` * `dlgProfilePreferences::setColorLightRed()` ==> `dlgProfilePreferences::slot_setColorLightRed()` * `dlgProfilePreferences::setColorLightRed2()` ==> `dlgProfilePreferences::slot_setMapColorLightRed()` * `dlgProfilePreferences::setColorLightWhite()` ==> `dlgProfilePreferences::slot_setColorLightWhite()` * `dlgProfilePreferences::setColorLightWhite2()` ==> `dlgProfilePreferences::slot_setMapColorLightWhite()` * `dlgProfilePreferences::setColorLightYellow()` ==> `dlgProfilePreferences::slot_setColorLightYellow()` * `dlgProfilePreferences::setColorLightYellow2()` ==> `dlgProfilePreferences::slot_setMapColorLightYellow()` * `dlgProfilePreferences::setColorMagenta()` ==> `dlgProfilePreferences::slot_setColorMagenta()` * `dlgProfilePreferences::setColorMagenta2()` ==> `dlgProfilePreferences::slot_setMapColorMagenta()` * `dlgProfilePreferences::setColorRed2()` ==> `dlgProfilePreferences::slot_setMapColorRed()` * `dlgProfilePreferences::setColorRed()` ==> `dlgProfilePreferences::slot_setColorRed()` * `dlgProfilePreferences::setColorWhite()` ==> `dlgProfilePreferences::slot_setColorWhite()` * `dlgProfilePreferences::setColorWhite2()` ==> `dlgProfilePreferences::slot_setMapColorWhite()` * `dlgProfilePreferences::setColorYellow()` ==> `dlgProfilePreferences::slot_setColorYellow()` * `dlgProfilePreferences::setColorYellow2()` ==> `dlgProfilePreferences::slot_setMapColorYellow()` * `dlgProfilePreferences::setCommandBgColor()` ==> `dlgProfilePreferences::slot_setCommandBgColor()` * `dlgProfilePreferences::setCommandFgColor()` ==> `dlgProfilePreferences::slot_setCommandFgColor()` * `dlgProfilePreferences::setCommandLineBgColor()` ==> `dlgProfilePreferences::slot_setCommandLineBgColor()` * `dlgProfilePreferences::setCommandLineFgColor()` ==> `dlgProfilePreferences::slot_setCommandLineFgColor()` * `dlgProfilePreferences::setDisplayFont()` ==> `dlgProfilePreferences::slot_setDisplayFont()` * `dlgProfilePreferences::setFgColor()` ==> `dlgProfilePreferences::slot_setFgColor()` * `dlgProfilePreferences::setFgColor2()` ==> `dlgProfilePreferences::slot_setMapExitsColor()` * `dlgProfilePreferences::setFontSize()` ==> `dlgProfilePreferences::slot_setFontSize()` * `dlgProfilePreferences::setMapInfoBackground()` ==> `dlgProfilePreferences::slot_setMapInfoBgColor()` * `dlgProfilePreferences::setRoomBorderColor()` ==> `dlgProfilePreferences::slot_setMapRoomBorderColor()` * `dlgProfilePreferences::slot_script_selected(...)` ==> `dlgProfilePreferences::slot_scriptSelected(...)` * `dlgProfilePreferences::slot_theme_selected(...)` ==> `dlgProfilePreferences::slot_themeSelected(...)` * `dlgRoomSymbol::colorRejected()` ==> `dlgRoomSymbol::slot_colorRejected()` * `dlgRoomSymbol::colorSelected(...)` ==> `dlgRoomSymbol::slot_colorSelected(...)` * `dlgRoomSymbol::currentColorChanged(...)` ==> `dlgRoomSymbol::slot_currentColorChanged(...)` * `dlgRoomSymbol::openColorSelector()` ==> `dlgRoomSymbol::slot_openColorSelector()` * `dlgRoomSymbol::resetColor()` ==> `dlgRoomSymbol::slot_resetColors()` * `dlgRoomSymbol::updatePreview()` ==> `dlgRoomSymbol::slot_updatePreview()` * `dlgTriggerEditor::slot_show_vars()` ==> `dlgTriggerEditor::slot_showVariables()` * `dlgTriggerEditor::slot_var_changed(...)` ==> `dlgTriggerEditor::slot_variableChanged(...)` * `dlgTriggerEditor::slot_var_selected(...)` ==> `dlgTriggerEditor::slot_variableSelected(...)` * `mudlet::slot_check_manual_update()` ==> `mudlet::slot_manualUpdateCheck()` * `mudlet::slot_close_current_profile()` ==> `mudlet::slot_closeCurrentProfile()` * `mudlet::slot_close_profile_requested(...)` ==> `mudlet::slot_closeProfileRequested(...)` * `mudlet::slot_connection_dlg_finished(...)` ==> `mudlet::slot_connectionDialogueFinished(...)` * `mudlet::slot_module_manager()` ==> `mudlet::slot_moduleManager()` * `mudlet::slot_mudlet_discord()` ==> `mudlet::slot_mudletDiscord()` * `mudlet::slot_multi_view(...)` ==> `mudlet::slot_multiView(const bool state)` * `mudlet::slot_package_manager()` ==> `mudlet::slot_packageManager()` * `mudlet::slot_package_exporter()` ==> `mudlet::slot_packageExporter()` * `mudlet::slot_timer_fires()` ==> `mudlet::slot_timerFires()` * `mudlet::slot_toggle_multi_view()` ==> `mudlet::slot_toggleMultiView()` Also the names for these in particular have been changed to make more sense: * `dlgProfilePreferences::slot_chooseProfilesChanged()` ==> `dlgProfilePreferences::slot_chosenProfilesChanged()` * `dlgProfilePreferences::slot_editor_tab_selected(...)` ==> `dlgProfilePreferences::slot_tabChanged(...)` * `dlgProfilePreferences::slot_passwords_location_changed(...)` ==> `dlgProfilePreferences::slot_passwordStorageLocationChanged(...)` * `dlgProfilePreferences::slot_save_and_exit()` ==> `dlgProfilePreferences::slot_saveAndClose()` * `dlgTriggerEditor::slot_toggleHiddenVar(...)` ==> `dlgTriggerEditor::slot_hideVariable(...)` - changed to distinguish it from `slot_toggleHiddenVariables(...)` * `dlgTriggerEditor::slot_item_selected_save(...)` ==> `dlgTriggerEditor::slot_saveSelectedItem(...)` * `mudlet::slot_discord()` ==> `mudlet::slot_profileDiscord()` - changed to distinguish it from `slot_mudletDiscord()` Not currently used and commented out: * `dlgProfilePreferences::setCommandLineFont()` ==> `dlgProfilePreferences::slot_setCommandLineFont()` Signed-off-by: Stephen Lyons <slysven@virginmedia.com>
2022-08-29 14:27:13 +02:00
void dlgConnectionProfiles::slot_copyProfile()
2009-01-30 01:25:11 -05:00
{
mCopyingProfile = true;
QString profile_name;
QString oldname;
QListWidgetItem* pItem;
const auto oldPassword = character_password_entry->text();
const CopiedProfileData data = captureProfileData();
if (!copyProfileWidget(profile_name, oldname, pItem)) {
mCopyingProfile = false;
return;
}
2010-03-15 09:37:16 +01:00
// A default profile (one of the predefined games) only exists in memory, so
// there is no folder to copy on-disk. Persist the displayed connection data
// into the new profile the same way saving a profile does, so the copy is
const QDir dir(mudlet::getMudletPath(enums::profileHomePath, oldname));
if (!dir.exists()) {
saveDefaultProfileCopy(profile_name, data, oldPassword);
return;
}
QApplication::setOverrideCursor(Qt::BusyCursor);
mpCopyProfile->setText(tr("Copying..."));
mpCopyProfile->setEnabled(false);
auto future = QtConcurrent::run(dlgConnectionProfiles::copyFolder, mudlet::getMudletPath(enums::profileHomePath, oldname), mudlet::getMudletPath(enums::profileHomePath, profile_name));
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
auto watcher = new QFutureWatcher<bool>(this);
connect(watcher, &QFutureWatcher<bool>::finished, this, [this, profile_name, oldPassword, watcher]() {
if (!mProfileList.contains(profile_name)) {
mProfileList << profile_name;
}
// The dialog stays usable while the copy runs, and switching the games
// tab calls fillout_form(), which destroys every item - including the
// one made for this copy. Hence look it up by name rather than hold it.
auto pCopiedItems = findData(*listWidget_profiles, profile_name, csmNameRole);
if (pCopiedItems.isEmpty()) {
// that rebuild scanned the profiles directory before the copy
// landed in it
fillout_form();
pCopiedItems = findData(*listWidget_profiles, profile_name, csmNameRole);
}
if (!pCopiedItems.isEmpty()) {
auto* pCopiedItem = pCopiedItems.first();
if (listWidget_profiles->currentItem() == pCopiedItem) {
slot_itemClicked(pCopiedItem);
} else {
// reaches slot_itemClicked() through currentItemChanged
listWidget_profiles->setCurrentItem(pCopiedItem);
}
}
// restore the password, which won't be copied by the disk copy if stored in the credential manager
// Temporarily block textChanged signal to avoid triggering save on programmatic setText
{
const QSignalBlocker blocker(character_password_entry);
character_password_entry->setText(oldPassword);
}
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 06:44:38 -04:00
if (mudlet::self()->storingPasswordsSecurely() && !oldPassword.trimmed().isEmpty()) {
writeSecurePassword(profile_name, oldPassword);
}
mCopyingProfile = false;
mpCopyProfile->setText(tr("Copy"));
mpCopyProfile->setEnabled(true);
QApplication::restoreOverrideCursor();
validateProfile();
watcher->deleteLater();
});
watcher->setFuture(future);
}
dlgConnectionProfiles::CopiedProfileData dlgConnectionProfiles::captureProfileData() const
{
return {host_name_entry->text(), port_entry->text(), port_ssl_tsl->isChecked() ? Qt::Checked : Qt::Unchecked, login_entry->text(), website_entry->text(), mud_description_textedit->toPlainText()};
}
// Copying a default profile (one of the predefined games) has nothing to copy
// on-disk, because such profiles only exist in memory until saved. Create the
// new profile's folder and persist the captured connection data, so the copy is
// a faithful, functional profile that survives reopening the connection screen.
// url/port/SSL go through the same writers used when saving a profile; the
// remaining fields are written directly.
void dlgConnectionProfiles::saveDefaultProfileCopy(const QString& profileName, const CopiedProfileData& data, const QString& oldPassword)
{
const QDir dir;
if (!dir.mkpath(mudlet::getMudletPath(enums::profileHomePath, profileName))) {
notificationArea->show();
notificationAreaIconLabelWarning->show();
notificationAreaIconLabelError->hide();
notificationAreaIconLabelInformation->hide();
notificationAreaMessageBox->show();
notificationAreaMessageBox->setText(tr("Could not create the new profile folder on your computer."));
mCopyingProfile = false;
return;
}
mProfileList << profileName;
// keep the copying flag up to the end: it stops the re-selection below
// from blanking the password field and stops validateProfile() from
// flagging the half-filled intermediate states
mCopyingProfile = true;
// the copy now exists on disk, so rebuilding the list shows it like any
// other saved profile - on a fresh install this also swaps the welcome
// message for the connection details, which would otherwise leave the
// copy invisible - then select it and fill in its details
fillout_form();
const auto pCopiedItems = findData(*listWidget_profiles, profileName, csmNameRole);
if (!pCopiedItems.isEmpty()) {
listWidget_profiles->setCurrentItem(pCopiedItems.first());
}
{
const QSignalBlocker nameBlocker(profile_name_entry);
const QSignalBlocker urlBlocker(host_name_entry);
const QSignalBlocker portBlocker(port_entry);
const QSignalBlocker sslBlocker(port_ssl_tsl);
const QSignalBlocker loginBlocker(login_entry);
profile_name_entry->setText(profileName);
host_name_entry->setText(data.host);
port_entry->setText(data.port);
port_ssl_tsl->setChecked(data.sslTsl == Qt::Checked);
login_entry->setText(data.login);
website_entry->setText(data.website);
website_entry->setVisible(!data.website.isEmpty());
mud_description_textedit->setPlainText(data.description);
}
slot_updateUrl(data.host);
slot_updatePort(data.port);
slot_updateSslTslPort(data.sslTsl);
if (!data.login.isEmpty()) {
writeProfileData(profileName, qsl("login"), data.login);
}
if (!data.website.isEmpty()) {
writeProfileData(profileName, qsl("website"), data.website);
}
if (!data.description.isEmpty()) {
writeProfileData(profileName, qsl("description"), data.description);
}
{
const QSignalBlocker blocker(character_password_entry);
character_password_entry->setText(oldPassword);
}
if (mudlet::self()->storingPasswordsSecurely() && !oldPassword.trimmed().isEmpty()) {
writeSecurePassword(profileName, oldPassword);
}
mCopyingProfile = false;
validateProfile();
}
Infrastructure: tidy up naming of SLOT methods and their usage - Part 5 (last) (#6266) When Qt's slot/signal system is used to invoke a method there is some overhead - so it makes sense to ensure developers can spot all such methods (functions). We have tried to do this with a `slot_` prefix to the methods we create but it has not been applied uniformly. This PR is intended to conclude helping with this by more rigorously doing so - the names changed herein should all follow a `slot_`*camelCaseMethodName* style. In addition some methods were detected that are not currently used or which are not actually used via the signal/slot system, these have been commented out or have had the prefix removed and the declaration in the relevant header file moved as appropriate. There were two similar sets of (not prefixed with `slot_`) slots in the Profile preferences class that adjusted the colour settings separately for the main console and the mapper the latter had the same names but with a `2` suffix. To made it more clear I have changed them to include `Map` in their names instead. Also, the NON-slot method: `(void) dlgProfilePreferences::setColor(QPushButton*, QColor&, bool)` has been renamed to: `(void) dlgProfilePreferences::setButtonAndProfileColor(QPushButton*, QColor&, bool)` so that it is clearly distinguishable from built in Qt methods that are also called `setColor` - though which do have different signatures! For reference the changes made are: * `TConsole::slot_stop_all_triggers(...)` ==> `TConsole::slot_stopAllItems(...)` * `dlgConnectionProfiles::slot_copy_profile()` ==> `dlgConnectionProfiles::slot_copyProfile()` * `dlgConnectionProfiles::slot_copy_profilesettings_only()` ==> `dlgConnectionProfiles::slot_copyOnlySettingsOfProfile()` * `dlgConnectionProfiles::slot_deleteprofile_check(...)` ==> `dlgConnectionProfiles::slot_deleteProfileCheck(...)` * `dlgConnectionProfiles::slot_password_deleted(...)` ==> `dlgConnectionProfiles::slot_passwordDeleted(...)` * `dlgConnectionProfiles::slot_password_saved(...) ==> `dlgConnectionProfiles::slot_passwordSaved(...)` * `dlgConnectionProfiles::slot_profile_menu(...)` ==> `dlgConnectionProfiles::slot_profileContextMenu(...)` * `dlgConnectionProfiles::slot_reset_custom_icon()` ==> `dlgConnectionProfiles::slot_resetCustomIcon()` * `dlgConnectionProfiles::slot_set_custom_icon()` ==> `dlgConnectionProfiles::slot_setCustomIcon()` * `dlgConnectionProfiles::slot_set_custom_color()` ==> `dlgConnectionProfiles::slot_setCustomColor()` * `dlgConnectionProfiles::slot_update_autologin(...)` ==> `dlgConnectionProfiles::slot_updateAutoConnect(...)` * `dlgConnectionProfiles::slot_update_autoreconnect(...)` ==> `dlgConnectionProfiles::slot_updateAutoReconnect(...)` * `dlgConnectionProfiles::slot_update_description()` ==> `dlgConnectionProfiles::slot_updateDescription()` * `dlgConnectionProfiles::slot_update_discord_optin(...)` ==> `dlgConnectionProfiles::slot_updateDiscordOptIn(...)` * `dlgProfilePreferences::copyMap()` ==> `dlgProfilePreferences::slot_copyMap()` * `dlgProfilePreferences::downloadMap()` ==> `dlgProfilePreferences::slot_downloadMap()` * `dlgProfilePreferences::hideActionLabel()` ==> `dlgProfilePreferences::slot_hideActionLabel()` * `dlgProfilePreferences::loadMap()` ==> `dlgProfilePreferences::slot_loadMap()` * `dlgProfilePreferences::resetColors()` ==> `dlgProfilePreferences::slot_resetColors()` * `dlgProfilePreferences::resetColors2()` ==> `dlgProfilePreferences::slot_resetMapColors()` * `dlgProfilePreferences::saveMap()` ==> `dlgProfilePreferences::slot_saveMap()` * `dlgProfilePreferences::setBgColor()` ==> `dlgProfilePreferences::slot_setBgColor()` * `dlgProfilePreferences::setBgColor2()` ==> `dlgProfilePreferences::slot_setMapBgColor()` * `dlgProfilePreferences::setColorBlack()` ==> `dlgProfilePreferences::slot_setColorBlack()` * `dlgProfilePreferences::setColorBlack2()` ==> `dlgProfilePreferences::slot_setMapColorBlack()` * `dlgProfilePreferences::setColorBlue()` ==> `dlgProfilePreferences::slot_setColorBlue()` * `dlgProfilePreferences::setColorCyan()` ==> `dlgProfilePreferences::slot_setColorCyan()` * `dlgProfilePreferences::setColorBlue2()` ==> `dlgProfilePreferences::slot_setMapColorBlue()` * `dlgProfilePreferences::setColorCyan2()` ==> `dlgProfilePreferences::slot_setMapColorCyan()` * `dlgProfilePreferences::setColorGreen()` ==> `dlgProfilePreferences::slot_setColorGreen()` * `dlgProfilePreferences::setColorGreen2()` ==> `dlgProfilePreferences::slot_setMapColorGreen()` * `dlgProfilePreferences::setColorLightBlack()` ==> `dlgProfilePreferences::slot_setColorLightBlack()` * `dlgProfilePreferences::setColorLightBlack2()` ==> `dlgProfilePreferences::slot_setMapColorLightBlack()` * `dlgProfilePreferences::setColorLightBlue()` ==> `dlgProfilePreferences::slot_setColorLightBlue()` * `dlgProfilePreferences::setColorLightBlue2()` ==> `dlgProfilePreferences::slot_setMapColorLightBlue()` * `dlgProfilePreferences::setColorLightCyan()` ==> `dlgProfilePreferences::slot_setColorLightCyan()` * `dlgProfilePreferences::setColorLightCyan2()` ==> `dlgProfilePreferences::slot_setMapColorLightCyan()` * `dlgProfilePreferences::setColorLightGreen()` ==> `dlgProfilePreferences::slot_setColorLightGreen()` * `dlgProfilePreferences::setColorLightGreen2()` ==> `dlgProfilePreferences::slot_setMapColorLightGreen()` * `dlgProfilePreferences::setColorLightMagenta()` ==> `dlgProfilePreferences::slot_setColorLightMagenta()` * `dlgProfilePreferences::setColorLightMagenta2()` ==> `dlgProfilePreferences::slot_setMapColorLightMagenta()` * `dlgProfilePreferences::setColorLightRed()` ==> `dlgProfilePreferences::slot_setColorLightRed()` * `dlgProfilePreferences::setColorLightRed2()` ==> `dlgProfilePreferences::slot_setMapColorLightRed()` * `dlgProfilePreferences::setColorLightWhite()` ==> `dlgProfilePreferences::slot_setColorLightWhite()` * `dlgProfilePreferences::setColorLightWhite2()` ==> `dlgProfilePreferences::slot_setMapColorLightWhite()` * `dlgProfilePreferences::setColorLightYellow()` ==> `dlgProfilePreferences::slot_setColorLightYellow()` * `dlgProfilePreferences::setColorLightYellow2()` ==> `dlgProfilePreferences::slot_setMapColorLightYellow()` * `dlgProfilePreferences::setColorMagenta()` ==> `dlgProfilePreferences::slot_setColorMagenta()` * `dlgProfilePreferences::setColorMagenta2()` ==> `dlgProfilePreferences::slot_setMapColorMagenta()` * `dlgProfilePreferences::setColorRed2()` ==> `dlgProfilePreferences::slot_setMapColorRed()` * `dlgProfilePreferences::setColorRed()` ==> `dlgProfilePreferences::slot_setColorRed()` * `dlgProfilePreferences::setColorWhite()` ==> `dlgProfilePreferences::slot_setColorWhite()` * `dlgProfilePreferences::setColorWhite2()` ==> `dlgProfilePreferences::slot_setMapColorWhite()` * `dlgProfilePreferences::setColorYellow()` ==> `dlgProfilePreferences::slot_setColorYellow()` * `dlgProfilePreferences::setColorYellow2()` ==> `dlgProfilePreferences::slot_setMapColorYellow()` * `dlgProfilePreferences::setCommandBgColor()` ==> `dlgProfilePreferences::slot_setCommandBgColor()` * `dlgProfilePreferences::setCommandFgColor()` ==> `dlgProfilePreferences::slot_setCommandFgColor()` * `dlgProfilePreferences::setCommandLineBgColor()` ==> `dlgProfilePreferences::slot_setCommandLineBgColor()` * `dlgProfilePreferences::setCommandLineFgColor()` ==> `dlgProfilePreferences::slot_setCommandLineFgColor()` * `dlgProfilePreferences::setDisplayFont()` ==> `dlgProfilePreferences::slot_setDisplayFont()` * `dlgProfilePreferences::setFgColor()` ==> `dlgProfilePreferences::slot_setFgColor()` * `dlgProfilePreferences::setFgColor2()` ==> `dlgProfilePreferences::slot_setMapExitsColor()` * `dlgProfilePreferences::setFontSize()` ==> `dlgProfilePreferences::slot_setFontSize()` * `dlgProfilePreferences::setMapInfoBackground()` ==> `dlgProfilePreferences::slot_setMapInfoBgColor()` * `dlgProfilePreferences::setRoomBorderColor()` ==> `dlgProfilePreferences::slot_setMapRoomBorderColor()` * `dlgProfilePreferences::slot_script_selected(...)` ==> `dlgProfilePreferences::slot_scriptSelected(...)` * `dlgProfilePreferences::slot_theme_selected(...)` ==> `dlgProfilePreferences::slot_themeSelected(...)` * `dlgRoomSymbol::colorRejected()` ==> `dlgRoomSymbol::slot_colorRejected()` * `dlgRoomSymbol::colorSelected(...)` ==> `dlgRoomSymbol::slot_colorSelected(...)` * `dlgRoomSymbol::currentColorChanged(...)` ==> `dlgRoomSymbol::slot_currentColorChanged(...)` * `dlgRoomSymbol::openColorSelector()` ==> `dlgRoomSymbol::slot_openColorSelector()` * `dlgRoomSymbol::resetColor()` ==> `dlgRoomSymbol::slot_resetColors()` * `dlgRoomSymbol::updatePreview()` ==> `dlgRoomSymbol::slot_updatePreview()` * `dlgTriggerEditor::slot_show_vars()` ==> `dlgTriggerEditor::slot_showVariables()` * `dlgTriggerEditor::slot_var_changed(...)` ==> `dlgTriggerEditor::slot_variableChanged(...)` * `dlgTriggerEditor::slot_var_selected(...)` ==> `dlgTriggerEditor::slot_variableSelected(...)` * `mudlet::slot_check_manual_update()` ==> `mudlet::slot_manualUpdateCheck()` * `mudlet::slot_close_current_profile()` ==> `mudlet::slot_closeCurrentProfile()` * `mudlet::slot_close_profile_requested(...)` ==> `mudlet::slot_closeProfileRequested(...)` * `mudlet::slot_connection_dlg_finished(...)` ==> `mudlet::slot_connectionDialogueFinished(...)` * `mudlet::slot_module_manager()` ==> `mudlet::slot_moduleManager()` * `mudlet::slot_mudlet_discord()` ==> `mudlet::slot_mudletDiscord()` * `mudlet::slot_multi_view(...)` ==> `mudlet::slot_multiView(const bool state)` * `mudlet::slot_package_manager()` ==> `mudlet::slot_packageManager()` * `mudlet::slot_package_exporter()` ==> `mudlet::slot_packageExporter()` * `mudlet::slot_timer_fires()` ==> `mudlet::slot_timerFires()` * `mudlet::slot_toggle_multi_view()` ==> `mudlet::slot_toggleMultiView()` Also the names for these in particular have been changed to make more sense: * `dlgProfilePreferences::slot_chooseProfilesChanged()` ==> `dlgProfilePreferences::slot_chosenProfilesChanged()` * `dlgProfilePreferences::slot_editor_tab_selected(...)` ==> `dlgProfilePreferences::slot_tabChanged(...)` * `dlgProfilePreferences::slot_passwords_location_changed(...)` ==> `dlgProfilePreferences::slot_passwordStorageLocationChanged(...)` * `dlgProfilePreferences::slot_save_and_exit()` ==> `dlgProfilePreferences::slot_saveAndClose()` * `dlgTriggerEditor::slot_toggleHiddenVar(...)` ==> `dlgTriggerEditor::slot_hideVariable(...)` - changed to distinguish it from `slot_toggleHiddenVariables(...)` * `dlgTriggerEditor::slot_item_selected_save(...)` ==> `dlgTriggerEditor::slot_saveSelectedItem(...)` * `mudlet::slot_discord()` ==> `mudlet::slot_profileDiscord()` - changed to distinguish it from `slot_mudletDiscord()` Not currently used and commented out: * `dlgProfilePreferences::setCommandLineFont()` ==> `dlgProfilePreferences::slot_setCommandLineFont()` Signed-off-by: Stephen Lyons <slysven@virginmedia.com>
2022-08-29 14:27:13 +02:00
void dlgConnectionProfiles::slot_copyOnlySettingsOfProfile()
{
QString profile_name;
QString oldname;
QListWidgetItem* pItem;
const auto oldPassword = character_password_entry->text();
const CopiedProfileData data = captureProfileData();
if (!copyProfileWidget(profile_name, oldname, pItem)) {
return;
}
const QDir oldProfileDir(mudlet::getMudletPath(enums::profileHomePath, oldname));
if (!oldProfileDir.exists()) {
saveDefaultProfileCopy(profile_name, data, oldPassword);
return;
}
const QDir newProfileDir(mudlet::getMudletPath(enums::profileHomePath, profile_name));
newProfileDir.mkpath(newProfileDir.path());
if (!newProfileDir.exists()) {
return;
}
// copy relevant profile files
Fix reduce c++20warnings (#7638) #### Brief overview of PR changes/additions 1. Adds explicit ‘this’ or ‘*this’ capture to lambdas where required (not in static ones). 2. Rephrases some combinations of Qt `enum`s that whilst acceptable for C++17 upset the later one. For the non-key related ones which are provided a `int`s arguments to a method this is sufficient; For Qt 6 there are some additional methods that overcome the "incompatibility" of combining such different `enum`s but those have not been back ported to the Qt 5.15.8 I have - despite what https://bugreports.qt.io/browse/QTBUG-99948 says - so fixes for combining QKey and QKeyModifier have been left out of this PR. 3. Removes a couple of unused variables. 4. Adds our `qsl(...)` wrapper about some raw C-string literals used in a loop. 5. Rewrite part of the qmake project file so the logic and choices are correct. #### Motivation for adding to Mudlet 1. To eliminate the following type of warning when building with a C++20 compiler: "warning: implicit capture of ‘this’ via ‘[=]’ is deprecated in C++20 [-Wdeprecated]" 2. To eliminate the following type of warning when building with a C++20 compiler: * warning: bitwise operation between different enumeration types ‘QFont::Weight’ and ‘QFont::StyleHint’ is deprecated [-Wdeprecated-enum-enum-conversion] * warning: bitwise operation between different enumeration types ‘Qt::TextFlag’ and ‘Qt::AlignmentFlag’ is deprecated [-Wdeprecated-enum-enum-conversion] 3. To eliminate the following type of warning when building with a C++20 compiler: "warning: unused variable ‘pHost’ [-Wunused-variable]" 4. To eliminate the following type of warning when building with a C++20 compiler: "warning: loop variable ‘file’ of type ‘const QString&’ binds to a temporary constructed from type ‘const char* const’ [-Wrange-loop-construct]" 5. The previous logic was (incorrect) IF Qt Major version is less than 5 OR (if Qt Major version is less than 6 AND if Qt Minor version is less than 12)) THEN add `-std=c++20` to `QMAKE_CXXFLAGS` ELSE add `c++2a` to `CONFIG` This is borked because we have already rejected Qt versions less than 5.14 so the logic will **always** end up in the **ELSE** case, and whilst Qt 5.15 is documented as accepting `CONFIG += c++2a` (and `c++2b`) the earliest Qt 6.x version describes `c++2a` as an obsolete alias for `c++20` (in https://doc.qt.io/qt-6.2/qmake-variable-reference.html). #### Other info (issues closed, discussion etc) 3. The code in the `QFont` cases contains errors that have been copied from bogus QFont creation code going back to the very first (well second) commit in the git history. This contained calls of the form `QFont font("Courier New", 10, QFont::Courier)` however even the Qt 4.8 documentation does not list a constructor of that form but instead has: `QFont(const QString & family, int pointSize = -1, int weight = -1, bool italic = false)` the third argument could possibly be `QFont::Normal` (50) or `QFont::Bold` (75) however the value of `QFont::Courier` is `2` but it is for a completely different purpose, that of the font matching strategy ("the font matcher prefers fixed pitch fonts.") but that is not something that can be set as an argument to the font constructor! As it happens the combination of `QFont::Bold | QFont::Serif | QFont::PreferMatch | QFont::PreferAntialias` that was being used numerically equals 75 + 2 + 32 + 128 = 227 - and the scale that Qt actually uses only goes from 0 to 99! 5. This change makes the code match the comments! Overall all the changes in this PR means that https://github.com/Mudlet/Mudlet/pull/7613 is not required after all - at least for Mudlet's own code - though there are still some warnings from the edbee-lib sub-module. --------- Signed-off-by: Stephen Lyons <slysven@virginmedia.com>
2025-02-10 11:59:45 +00:00
for (const QString& file : {qsl("url"), qsl("port"), qsl("password"), qsl("login"), qsl("description")}) {
auto filePath = qsl("%1/%2").arg(mudlet::getMudletPath(enums::profileHomePath, oldname), file);
auto newFilePath = qsl("%1/%2").arg(mudlet::getMudletPath(enums::profileHomePath, profile_name), file);
QFile::copy(filePath, newFilePath);
}
copyProfileSettingsOnly(oldname, profile_name);
mProfileList << profile_name;
Infrastructure: tidy up naming of SLOT methods and their usage - Part 2 (#6255) When Qt's slot/signal system is used to invoke a method there is some overhead - so it makes sense to ensure developers can spot all such methods (functions). We have tried to do this with a `slot_` prefix to the methods we create but it has not been applied uniformly. This PR (and one or more to follow) is intended to help with this by more rigorously doing so - the names changed herein should all follow a `slot_`*camelCaseMethodName* style. In addition some methods were detected that are not currently used or which are not actually used via the signal/slot system, these have been commented out or have had the prefix removed and the declaration in the relevant header file moved as appropriate. For reference the changes made are: * `TLuaInterpreter::slotDeleteSender(...)` ==> `TLuaInterpreter::slot_deleteSender(...)` * `TLuaInterpreter::slotPurge()` ==> `TLuaInterpreter::slot_purge()` * `cTelnet::handle_socket_signal_connected()` ==> `cTelnet::slot_socketConnected()` * `cTelnet::handle_socket_signal_disconnected()` ==> `cTelnet::slot_socketDisconnected()` * `cTelnet::handle_socket_signal_hostFound(...)` ==> `cTelnet::slot_socketHostFound(...)` * `cTelnet::handle_socket_signal_readyRead()` ==> `cTelnet::slot_socketReadyToBeRead()` * `cTelnet::handle_socket_signal_sslError(...)` ==> `cTelnet::slot_socketSslError(...)` * `cTelnet::setDownloadProgress(...)` ==> `cTelnet::slot_setDownloadProgress(...)` * `dlgComposer::cancel()` ==> `dlgComposer::slot_cancel()` * `dlgComposer::save()` ==> `dlgComposer::slot_save()` * `dlgConnectionProfiles::slot_item_clicked(...)` ==> `dlgConnectionProfiles::slot_itemClicked(...)` * `dlgConnectionProfiles::slot_save_name()` ==> `dlgConnectionProfiles::slot_saveName()` * `dlgConnectionProfiles::slot_update_login(...)` ==> `dlgConnectionProfiles::slot_updateLogin(...)` * `dlgConnectionProfiles::slot_update_name(...)` ==> `dlgConnectionProfiles::slot_updateName(...)` * `dlgConnectionProfiles::slot_update_pass(...)` ==> `dlgConnectionProfiles::slot_updatePassword(...)` * `dlgConnectionProfiles::slot_update_port(...)` ==> `dlgConnectionProfiles::slot_updatePort(...)` * `dlgConnectionProfiles::slot_update_SSL_TSL_port(...)` ==> `dlgConnectionProfiles::slot_updateSslTslPort(...)` * `dlgConnectionProfiles::slot_update_url(...)` ==> `dlgConnectionProfiles::slot_updateUrl(...)` * `dlgMapLabel::pickBgColor()` ==> `dlgMapLabel::slot_pickBgColor()` * `dlgMapLabel::pickFgColor()` ==> `dlgMapLabel::slot_pickFgColor()` * `dlgMapLabel::pickFile()` ==> `dlgMapLabel::slot_pickFile()` * `dlgMapLabel::pickFont()` ==> `dlgMapLabel::slot_pickFont()` * `dlgMapLabel::save()` ==> `dlgMapLabel::slot_save()` * `dlgMapLabel::updateControls()` ==> `dlgMapLabel::slot_updateControls()` * `dlgMapLabel::updateControlsVisibility()` ==> `dlgMapLabel::slot_updateControlsVisibility()` Commented out as not being used: * `cTelnet::handle_socket_signal_error()` ==> `cTelnet::slot_socketError()` * `dlgConnectionProfiles::slot_update_website(...) ==> `dlgConnectionProfiles::slot_updateWebsite(const QString& url)` I have my doubts about whether the `dlgMapLabel::save()`/`dlgMapLabel::slot_save()` method is needed at all, as it might be that the `QDialog::accept()` slot could have been inserted into the `QObject::connect(...)` call directly instead...? Signed-off-by: Stephen Lyons <slysven@virginmedia.com>
2022-08-27 10:47:01 +02:00
slot_itemClicked(pItem);
}
bool dlgConnectionProfiles::copyProfileWidget(QString& profile_name, QString& oldname, QListWidgetItem*& pItem) const
{
profile_name = profile_name_entry->text().trimmed();
oldname = profile_name;
if (profile_name.isEmpty()) {
return false;
}
// prepend n+1 to end of the profile name
if (profile_name.at(profile_name.size() - 1).isDigit()) {
int i = 1;
do {
profile_name = profile_name.left(profile_name.size() - 1) + QString::number(profile_name.at(profile_name.size() - 1).digitValue() + i++);
} while (mProfileList.contains(profile_name));
} else {
int i = 1;
QString profile_name2;
do {
profile_name2 = profile_name + QString::number(i++);
} while (mProfileList.contains(profile_name2));
profile_name = profile_name2;
}
pItem = new (std::nothrow) QListWidgetItem();
if (!pItem) {
return false;
}
Revise: select profiles by typing the name, keep profile icons (#4440) Restore the text display of the profile name. This is so that the keyboard selection of profiles beginning with a particular letter works again. This will close issue #4283 . This also sets it to the minimum size trying to hide it by setting the font size to 1 - however that still show something (and a font size of zero is NOT valid). Manipulating the QPalette of the QListWidget does not work well enough in the presence of Styles or StyleSheets to be a viable means of hiding the text either. Also in the `dlgConnection` class: * refactor out some highly repetitive code that sets up the predefined Mud icons. * remove warnings and adopts a change introduced into Qt 5.15 for the QLabel::pixmap() which now requires a Qt::ReturnByValue argument to prevent an obsolete code warning and to force a QPixmap to be returned by value and not reference... * remove a warning about: QListWidget::setItemSelected(QListWidgetItem*,bool) being deprecated and switches to the recommended: QListWidgetItem::setSelected(bool) Edit: the last item above may have become redundant as further commits amalgamated into this PR may have taken out these lines anyhow! Change the custom icon generator so that there is NOT a dependency on the position in the list for the colours used. This means that the same name will always have the same colours - which will help the user to pick out a particular profile as it will always look the same no matter if other profiles are added or removed. Remove the colour gradient in the custom icon as it is not thought to be useful nowadays. Refactor out the colour icon generator to a single method that is called from more than one place. BugFix: restore odd original way of accessing some QPixmaps This was causing failures in some CI platforms using Qt 5.14. or older. Revise: really frob the custom icon generator This is to try and produce as varied but still readable icons when they are generated entirely on the basis of the MUD name - in a reproducible (though Qt do NOT guarantee repeatable qHash() functionality across Qt versions - so they may not stay *that* constant) manner. Refrob: use a table of fixed colours Using only 15 hues but with 3 different saturation level plus 5 grey-scale one should give enough different icons but not generate them too close to each other for those with normal colour vision... Signed-off-by: Stephen Lyons <slysven@virginmedia.com> Co-authored-by: Kebap <kebap_spam@gmx.net>
2020-12-11 17:53:28 +00:00
setItemName(pItem, profile_name);
// add the new widget in
listWidget_profiles->addItem(pItem);
pItem->setIcon(customIcon(profile_name, std::nullopt));
listWidget_profiles->setCurrentItem(pItem);
profile_name_entry->setText(profile_name);
profile_name_entry->setFocus();
profile_name_entry->selectAll();
profile_name_entry->setReadOnly(false);
host_name_entry->setReadOnly(false);
port_entry->setReadOnly(false);
return true;
}
void dlgConnectionProfiles::copyProfileSettingsOnly(const QString& oldname, const QString& newname)
{
const QDir oldProfiledir(mudlet::getMudletPath(enums::profileXmlFilesPath, oldname));
const QDir newProfiledir(mudlet::getMudletPath(enums::profileXmlFilesPath, newname));
newProfiledir.mkpath(newProfiledir.absolutePath());
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
// Only copy from a real profile save (*.xml): the newest file of any name could
// be a leftover QSaveFile temporary from an interrupted save (e.g. "....xml.AbCdEf")
QStringList entries = oldProfiledir.entryList(QStringList{qsl("*.xml")}, QDir::Files | QDir::NoDotAndDotDot, QDir::Time);
if (entries.empty()) {
2009-01-30 01:25:11 -05:00
return;
}
2010-03-15 09:37:16 +01:00
auto copySettingsFromFile = oldProfiledir.absoluteFilePath(entries.first());
pugi::xml_document newProfileXml;
if (extractSettingsFromProfile(newProfileXml, copySettingsFromFile)) {
saveProfileCopy(newProfiledir, newProfileXml);
}
}
bool dlgConnectionProfiles::extractSettingsFromProfile(pugi::xml_document& newProfile, const QString& copySettingsFrom)
{
pugi::xml_document oldProfile;
pugi::xml_parse_result const result = oldProfile.load_file(copySettingsFrom.toUtf8().constData());
if (!result) {
qWarning() << "dlgConnectionProfiles::copyProfileSettingsOnly() ERROR: couldn't parse" << copySettingsFrom;
qWarning() << "Parse error: " << result.description() << ", character pos= " << result.offset;
return false;
}
// write header
auto declaration = newProfile.prepend_child(pugi::node_declaration);
declaration.append_attribute("version") = "1.0";
declaration.append_attribute("encoding") = "UTF-8";
newProfile.append_child(pugi::node_doctype).set_value("MudletPackage");
// copy /MudletPackage attributes
auto mudletPackage = newProfile.append_child("MudletPackage");
const auto attributeNodes = oldProfile.select_nodes("/MudletPackage/attribute::*");
for (pugi::xpath_node_set::const_iterator it = attributeNodes.begin(); it != attributeNodes.end(); ++it) {
auto node = *it;
mudletPackage.append_attribute(node.attribute().name()) = node.attribute().value();
}
// remove installed packages/modules
const auto hostPackageResults = oldProfile.select_nodes("/MudletPackage/HostPackage");
pugi::xml_node const hostPackage = hostPackageResults.first().node();
auto host = hostPackage.child("Host");
host.remove_child("mInstalledPackages");
host.remove_child("mInstalledModules");
// copy in the /Mudlet/HostPackage
mudletPackage.append_copy(hostPackage);
return true;
}
// save profile using Qt's API's which handle non-ASCII characters in Windows paths fine
void dlgConnectionProfiles::saveProfileCopy(const QDir& newProfiledir, const pugi::xml_document& newProfileXml) const
{
QSaveFile file(newProfiledir.absoluteFilePath(qsl("Copied profile (settings only).xml")));
if (!file.open(QFile::WriteOnly)) {
qDebug() << "dlgConnectionProfiles::copyProfileSettingsOnly ERROR - couldn't create new profile file:" << file.fileName() << "-" << file.errorString();
return;
}
std::stringstream saveStringStream(std::ios::out);
newProfileXml.save(saveStringStream);
std::string output(saveStringStream.str());
file.write(output.data());
if (!file.commit()) {
qDebug() << "dlgConnectionProfiles::saveProfileCopy: error copying profile: " << file.errorString();
}
2009-01-30 01:25:11 -05:00
}
void dlgConnectionProfiles::loadProfile(bool alsoConnect)
2009-01-24 02:50:22 +01:00
{
const QString profile_name = profile_name_entry->text().trimmed();
2010-03-15 09:37:16 +01:00
if (profile_name.isEmpty()) {
return;
}
2010-03-15 09:37:16 +01:00
2025-08-18 09:31:03 -04:00
// Check if the host already exists before calling mudlet::loadProfile()
Host* pHostBeforeLoad = mudlet::self()->getHostManager().getHost(profile_name);
bool hostExistedBefore = (pHostBeforeLoad != nullptr);
Host* pHost = mudlet::self()->loadProfile(profile_name, alsoConnect, profile_history->currentData().toString());
2010-03-15 09:37:16 +01:00
// overwrite the generic profile with user supplied name, url and login information
if (pHost) {
Host* pActiveHost = mudlet::self()->getActiveHost();
2025-09-13 14:17:13 +07:00
if (pActiveHost && pActiveHost->getName() == profile_name) {
2025-08-18 09:31:03 -04:00
// Skip reconnect if mudlet::loadProfile already connected for existing hosts
if (alsoConnect && hostExistedBefore) {
QDialog::accept();
return;
}
// Reconnect to the active profile
pActiveHost->mTelnet.reconnect();
QDialog::accept();
return;
}
2025-09-13 14:17:13 +07:00
2025-08-18 09:31:03 -04:00
// Skip signal emission if mudlet::loadProfile already handled the connection
if (alsoConnect && hostExistedBefore) {
QDialog::accept();
return;
}
pHost->setName(profile_name);
Enhance: add text transcoding (#969) * Enhance: add encoding/decoding to incoming and outgoing MUD data Found during testing on the Spanish Realms of Legends MUD that we did not support ISO-8859-1 (Latin-1) encoding but could if we made a small change to the way we process the incoming text bytes in the TBuffer::translateToPlainText(...) method. Instead of appending each byte as a char onto TBuffer::mMudline - which works for (7-bit) ASCII characters we can convert the upper 128 code characters to the "correct" QChars by going via a QChar::fromLatin1(...) intermediate step. It was then found possible to extend this process for other simple encoding tables by recording the differences between the ISO 8859-1 and various other encodings. Included in this commit is support for: * ISO 8859-1 * ISO 8859-2 * ISO 8859-3 * ISO 8859-4 * ISO 8859-10 * ISO 8859-15 * ISO 8859-16 * WINDOWS-1250 * WINDOWS-1251 * WINDOWS-1252 The above (and UTF-8) can be selected from the profile preferences and will be saved on a per profile basis once set and used when the profile is used again. UTF-8 is manually decoded by examining the first byte to determine how many more bytes are expected for it and then checking all of the indicated bytes for validity. Checks are included to guard against invalid code points (beyond U+10FFFF) and "overlong" sequences. During testing against some data from a real MUD it was found that the UTF-8 BOM sequence although not required or recommended for this encoding was being converted to no QChars at all, technically the position in a stream where it occurs could replace it with some form of joiner or it could actually be discarded however a script or trigger might be interested in it so the equivalent QChar is manually reinserted into the decoded stream - this is also useful as the code structure wants to insert a TChar instance into the corresponding data storage every time around the parsing loop for a single byte (for all but UTF-8) encoding or Utf-8 byte sequence for a codepoint (for UTF-8). NOTE THAT THE DECODER WILL HANDLE NON-BMP BYTE SEQUENCES AND DECODES THEM TO TWO QChars (a High surrogate followed by a Low one) AS QT DOES (It is based on QStrings that are Utf-16). To allow code that follows this stage to keep a one-to-one match between QChars in the text data and TChars that record the rendering aspects (colour, font styling) a duplicate TChar is added after the normal one for non-BMP codepoints. I feel this will help to port the existing code over to support such non-BMP codepoints...! Found missing braces was allowing some code to execute when the item it was trying to reference did not exist. Added getServerEncodingsList() which provides a list with the exact strings to use - and which allows us to revised them if necessary. Revise setServerEncoding() to not regard setting the same encoding that is already being used from being a run-time error - now it will just return the same true as if the setting had been made - but as no change is actually taking place, no message will appear on the main console. This message is now an "[ OK ] -" form one without an ending ellipses whereas it was an "[ ALERT ] -" with one. Whether a change does occur or not a text message is not provided, only on the run-time error case of specifying an incorrect encoding will a nil + error-message now be produced. Comment at start of TBuffer::translateToPlainText(...) redone. Renamed data input argument to above function to ensure that the local version which can be slightly longer if there are bytes "left-over" from the previous packet that have been prepended to it. Doing this allowed me to find and fix places where that was not being used. Revised the error handling for where the decoded UTF-8 data yields more than two QChars from when converted by a QString constructor - it should not happen but being paranoid is not unreasonable with initial coding attempts...! Also arranged that more error cases report the actual bytes that seemed to be in error. Revised: use hard coded tables for decoding MUD Server data Now using data recovered from Qt's own code repository, and adds additional encodings - which could be subsequently added/removed as we see fit. Code also restructured to work with a singleton(?) table of the above data, in the form of a QMap<QString, QVector<QChar>> where the QVector is the 128 Unicode code-point values for the characters in the range 128-255 for each of the Extended ASCII tables. This excludes ISO 8859-1 as that is implicitly the one-to-one mapping 128=128, 129=129,... 255=255 and obviously excludes ASCII which does not go that far and UTF-8 which is handled differently. It also allows a significant performance improvement as it eliminates a pair of switch() constructs and a call to a encoding specific method for each byte of data for the other encoding and replaces them with a single look-up in a single QVector; that being initialised at the start of processing the packet of data. I made an error in assessing what the bit pattern for the highest valid code-point value is for the bit pattern for the 4-bytes associated with it. It is also not acceptable to have UTF-16 Surrogates in a normal UTF-8 data- steam and best-practice is to reject them. Added a compilation conditional macro: "DEBUG_UTF8_PROCESSING" to hide possibly spammy qDebug() messages related to the decoding of incoming UTF-8 text. It IS defined after this commit. Fixed a coding bug that would lose 1 to 3 bytes of server data should a UTF-8 byte stream from the MUD server be fragmented between two incoming network data packets. It also adds a warning in the tool-tip of the encoding setting control to leave the encoding setting on the profile preferences to be ASCII or ISO 8859-1 as others will not yet work with the remainder of the application's code base. Some phrasing in the somewhat lengthy text of that tool-tip (suggestions are being considered as to "precis"ing it to a smaller version!) have also been made. The name of the setting has also been change from "Text encoding" to "Server Data Encoding" to (I feel) better indicate it's purpose. The tables containing the encoding of Extended ASCII character codes to the representation as Unicode code-points for "other" codecs that ISO-8859-1 are laid out carefully so that individual entries can be identified as best they can. However the current (any?) clang-format configuration will destroy the layout so I had deliberately marked them as not be touched by that tool. Changed to use a C++11 ranged "for" instead of Qt macro "foreach" in TBuffer constructor. Changed format of run-time error for an invalid "encoding" string supplied to to the Lua setServerEncoding("encoding") function. Reordered encodings list so that UTF-8 follows immediately after ASCII rather than being on the end of the list. Run-time error string on supplying a bad encoding to setServerEncoding(...) is now: "Encoding \"XXXX\" does not exist; use one of the following: \"ASCII\", <list of other encodings (with UTF-8 at the top)>." Profile Preferences title for encoding is now "Server data encoding" - it has lost the extra capitalisation I originally used. 8-| If the last bytes of a packet are multi-byte UTF-8 sequence it is necessary to store them if there is not enough bytes in the packet to complete the processing of them in TBuffer::translateToPlainText(...). Added a couple of QDebug()s - conditional on DEBUG_UTF8_PROCESSING that: report that an incomplete sequence has been detected and how many bytes are being stored; and report when they are being prepended onto the data in the next call to the method concerned. Whilst using Mudlet replays to debug this issue I noticed that the replay starting and ending messages did not have the classification tag of the form "[ XXXX ] -" so did not display in any special way, I revised them to become an "[ INFO ]" message for replay starting and "[ OK ]" at the end...! Found a local automatic variable "msPos" in TBuffer::trasnlateToPlainText(...) was shadowing an unnecessary TBuffer class member so renamed/retyped the local to be more appropriate, also found that a second class member was not needed outside the method so added a local automatic to do the same job: * (int) msPos ==> (size_t) localBufferPosition * msLength ==> (size_t) localBufferLength Also remove now unused and/or shadowed: * (int) TBuffer::lookupColor(const QString&, int) * (int) TBuffer::msLength * (int) TBuffer::msPos * (QString) TBuffer::mFormatSequenceRest I found that there was some MXP related code that was still working on the incoming string data rather than the possibly pre-pended local copy which would cause (further) problems in the future for non-ASCII that use MXP! I changed those to also work on the local version. Within TBuffer::translateToPlainText(...) I also spotting some single line if(...) {...} else {...} which I braced as appropriate, a couple of places where different spacing helps to line up related code and some other redundant code fragments which I removed. This comment was edited from the totality of all the commits that were squashed together to make a single commit. Signed-off-by: Stephen Lyons <slysven@virginmedia.com>
2017-05-22 16:31:25 +01:00
if (!host_name_entry->text().trimmed().isEmpty()) {
pHost->setUrl(host_name_entry->text().trimmed());
} else {
Infrastructure: tidy up naming of SLOT methods and their usage - Part 2 (#6255) When Qt's slot/signal system is used to invoke a method there is some overhead - so it makes sense to ensure developers can spot all such methods (functions). We have tried to do this with a `slot_` prefix to the methods we create but it has not been applied uniformly. This PR (and one or more to follow) is intended to help with this by more rigorously doing so - the names changed herein should all follow a `slot_`*camelCaseMethodName* style. In addition some methods were detected that are not currently used or which are not actually used via the signal/slot system, these have been commented out or have had the prefix removed and the declaration in the relevant header file moved as appropriate. For reference the changes made are: * `TLuaInterpreter::slotDeleteSender(...)` ==> `TLuaInterpreter::slot_deleteSender(...)` * `TLuaInterpreter::slotPurge()` ==> `TLuaInterpreter::slot_purge()` * `cTelnet::handle_socket_signal_connected()` ==> `cTelnet::slot_socketConnected()` * `cTelnet::handle_socket_signal_disconnected()` ==> `cTelnet::slot_socketDisconnected()` * `cTelnet::handle_socket_signal_hostFound(...)` ==> `cTelnet::slot_socketHostFound(...)` * `cTelnet::handle_socket_signal_readyRead()` ==> `cTelnet::slot_socketReadyToBeRead()` * `cTelnet::handle_socket_signal_sslError(...)` ==> `cTelnet::slot_socketSslError(...)` * `cTelnet::setDownloadProgress(...)` ==> `cTelnet::slot_setDownloadProgress(...)` * `dlgComposer::cancel()` ==> `dlgComposer::slot_cancel()` * `dlgComposer::save()` ==> `dlgComposer::slot_save()` * `dlgConnectionProfiles::slot_item_clicked(...)` ==> `dlgConnectionProfiles::slot_itemClicked(...)` * `dlgConnectionProfiles::slot_save_name()` ==> `dlgConnectionProfiles::slot_saveName()` * `dlgConnectionProfiles::slot_update_login(...)` ==> `dlgConnectionProfiles::slot_updateLogin(...)` * `dlgConnectionProfiles::slot_update_name(...)` ==> `dlgConnectionProfiles::slot_updateName(...)` * `dlgConnectionProfiles::slot_update_pass(...)` ==> `dlgConnectionProfiles::slot_updatePassword(...)` * `dlgConnectionProfiles::slot_update_port(...)` ==> `dlgConnectionProfiles::slot_updatePort(...)` * `dlgConnectionProfiles::slot_update_SSL_TSL_port(...)` ==> `dlgConnectionProfiles::slot_updateSslTslPort(...)` * `dlgConnectionProfiles::slot_update_url(...)` ==> `dlgConnectionProfiles::slot_updateUrl(...)` * `dlgMapLabel::pickBgColor()` ==> `dlgMapLabel::slot_pickBgColor()` * `dlgMapLabel::pickFgColor()` ==> `dlgMapLabel::slot_pickFgColor()` * `dlgMapLabel::pickFile()` ==> `dlgMapLabel::slot_pickFile()` * `dlgMapLabel::pickFont()` ==> `dlgMapLabel::slot_pickFont()` * `dlgMapLabel::save()` ==> `dlgMapLabel::slot_save()` * `dlgMapLabel::updateControls()` ==> `dlgMapLabel::slot_updateControls()` * `dlgMapLabel::updateControlsVisibility()` ==> `dlgMapLabel::slot_updateControlsVisibility()` Commented out as not being used: * `cTelnet::handle_socket_signal_error()` ==> `cTelnet::slot_socketError()` * `dlgConnectionProfiles::slot_update_website(...) ==> `dlgConnectionProfiles::slot_updateWebsite(const QString& url)` I have my doubts about whether the `dlgMapLabel::save()`/`dlgMapLabel::slot_save()` method is needed at all, as it might be that the `QDialog::accept()` slot could have been inserted into the `QObject::connect(...)` call directly instead...? Signed-off-by: Stephen Lyons <slysven@virginmedia.com>
2022-08-27 10:47:01 +02:00
slot_updateUrl(pHost->getUrl());
}
2010-03-15 09:37:16 +01:00
if (!port_entry->text().trimmed().isEmpty()) {
pHost->setPort(port_entry->text().trimmed().toInt());
} else {
Infrastructure: tidy up naming of SLOT methods and their usage - Part 2 (#6255) When Qt's slot/signal system is used to invoke a method there is some overhead - so it makes sense to ensure developers can spot all such methods (functions). We have tried to do this with a `slot_` prefix to the methods we create but it has not been applied uniformly. This PR (and one or more to follow) is intended to help with this by more rigorously doing so - the names changed herein should all follow a `slot_`*camelCaseMethodName* style. In addition some methods were detected that are not currently used or which are not actually used via the signal/slot system, these have been commented out or have had the prefix removed and the declaration in the relevant header file moved as appropriate. For reference the changes made are: * `TLuaInterpreter::slotDeleteSender(...)` ==> `TLuaInterpreter::slot_deleteSender(...)` * `TLuaInterpreter::slotPurge()` ==> `TLuaInterpreter::slot_purge()` * `cTelnet::handle_socket_signal_connected()` ==> `cTelnet::slot_socketConnected()` * `cTelnet::handle_socket_signal_disconnected()` ==> `cTelnet::slot_socketDisconnected()` * `cTelnet::handle_socket_signal_hostFound(...)` ==> `cTelnet::slot_socketHostFound(...)` * `cTelnet::handle_socket_signal_readyRead()` ==> `cTelnet::slot_socketReadyToBeRead()` * `cTelnet::handle_socket_signal_sslError(...)` ==> `cTelnet::slot_socketSslError(...)` * `cTelnet::setDownloadProgress(...)` ==> `cTelnet::slot_setDownloadProgress(...)` * `dlgComposer::cancel()` ==> `dlgComposer::slot_cancel()` * `dlgComposer::save()` ==> `dlgComposer::slot_save()` * `dlgConnectionProfiles::slot_item_clicked(...)` ==> `dlgConnectionProfiles::slot_itemClicked(...)` * `dlgConnectionProfiles::slot_save_name()` ==> `dlgConnectionProfiles::slot_saveName()` * `dlgConnectionProfiles::slot_update_login(...)` ==> `dlgConnectionProfiles::slot_updateLogin(...)` * `dlgConnectionProfiles::slot_update_name(...)` ==> `dlgConnectionProfiles::slot_updateName(...)` * `dlgConnectionProfiles::slot_update_pass(...)` ==> `dlgConnectionProfiles::slot_updatePassword(...)` * `dlgConnectionProfiles::slot_update_port(...)` ==> `dlgConnectionProfiles::slot_updatePort(...)` * `dlgConnectionProfiles::slot_update_SSL_TSL_port(...)` ==> `dlgConnectionProfiles::slot_updateSslTslPort(...)` * `dlgConnectionProfiles::slot_update_url(...)` ==> `dlgConnectionProfiles::slot_updateUrl(...)` * `dlgMapLabel::pickBgColor()` ==> `dlgMapLabel::slot_pickBgColor()` * `dlgMapLabel::pickFgColor()` ==> `dlgMapLabel::slot_pickFgColor()` * `dlgMapLabel::pickFile()` ==> `dlgMapLabel::slot_pickFile()` * `dlgMapLabel::pickFont()` ==> `dlgMapLabel::slot_pickFont()` * `dlgMapLabel::save()` ==> `dlgMapLabel::slot_save()` * `dlgMapLabel::updateControls()` ==> `dlgMapLabel::slot_updateControls()` * `dlgMapLabel::updateControlsVisibility()` ==> `dlgMapLabel::slot_updateControlsVisibility()` Commented out as not being used: * `cTelnet::handle_socket_signal_error()` ==> `cTelnet::slot_socketError()` * `dlgConnectionProfiles::slot_update_website(...) ==> `dlgConnectionProfiles::slot_updateWebsite(const QString& url)` I have my doubts about whether the `dlgMapLabel::save()`/`dlgMapLabel::slot_save()` method is needed at all, as it might be that the `QDialog::accept()` slot could have been inserted into the `QObject::connect(...)` call directly instead...? Signed-off-by: Stephen Lyons <slysven@virginmedia.com>
2022-08-27 10:47:01 +02:00
slot_updatePort(QString::number(pHost->getPort()));
}
Update: remove Midkemia (and add WoTMUD) to list of predefined MUDs (#321) * Update: remove Midkemia (and add WoTMUD) to list of predefined MUDs The I.R.E. MUD Midkemia-online was shutdown on 2016/09/07 and it is not helpful to continue to offer it as a predefined MUD in the connection dialog. This commit removes it from that list and adds a favourite of mine "WoTMUD" to replace it. The logo is a temporary one that I will replace with a similar one that I need to get clearance to use (and release with a GPL2.1+ licence) from the Wheel of Time MUD sysop with whom I am in contact {Vivienne needs to get approval from the creator Flash who is away for a few days...!} The dlgConnectionProfiles.cpp file has quite a few ASCII strings, as appropriate for a file that will in the future be subject to I18n work I took the opportunity to put QStringLiteral(...) wrappers around the QStrings that are used for non-GUI tasks and tr(...) ones that are. I expect it isn't complete but it goes someway towards dealing with THIS particular file - and it should improve very slightly the generation of QStrings that need to be constructed because it means as much of possible of them is done at compile rather than run-time...! In relation to the above there are points in the code where QStrings are compared to "" to see if they do not have any contents - the isEmpty() method is a better way to do the same (it does not involve a string expression) In (void)dlgConnectionProfiles::slot_update_name( const QString ) there are a couple of variables that use a double underscore prefix - that is NOT A Good Idea: such a prefix is RESERVED in both C and C++. For example see: [what-are-the-rules-about-using-an-underscore-in-a-c-identifier](http://stackoverflow.com/questions/228783/what-are-the-rules-about-using-an-underscore-in-a-c-identifier) also a single prefix might be a bit of a problem with libraries... I have renames the offending things there - but I may have used the single underscore prefix myself in the past. *blush* There are some help messages in this class that use an apostrophe in a word contraction - as a matter of readability this is not considered best practice IMHO so I have converted them to the full words. Following experiences in my previous Pull Request I also took a look at the initialisation and use of some member variables; it became clear that: * (QString) dlgConnectionProfiles::mOrigin * (bool) dlgConnectionProfiles::mEditOK were not used and could be simply deleted and that: * (QString) dlgConnectionProfiles::mUnsavedProfileName * (QString) dlgConnectionProfiles::mCurrentProfileEditName did not need to be member variables and could be local to the method that used them, so in (void)dlgConnectionProfiles::slot_save_name(): * (QString) dlgConnectionProfiles::mCurrentProfileEditName becomes local: (QString) currentProfileEditName and in (void) dlgConnectionProfiles::slot_addProfile(): * (QString) dlgConnectionProfiles::mUnsavedProfileName becomes local: (QString) newName also added initialisers for: * (QStringList) dlgConnectionProfiles::mProfileList * (QPushButton *) dlgConnectionProfiles::connect_button * (QLineEditQPushButton *) dlgConnectionProfiles::delete_profile_lineedit * (QPushButton *) dlgConnectionProfiles::delete_button Signed-off-by: Stephen Lyons <slysven@virginmedia.com> * More: two items missed from prior commit In the previous commit I forgot to take out: * a check for Midkemia in the dlgProfilePreferences class constructor that enabled the download map options; * a similar check in (bool)TMap::restore(QString) that asked if the user wanted to download a map if there was not one found. Signed-off-by: Stephen Lyons <slysven@virginmedia.com> * Tweak: Insert MUD supplied Logo/Icon Received an icon as a .jpg that the MUD operators can allow us to licence as GPL2.1+ which I have converted to a .png file. Signed-off-by: Stephen Lyons <slysven@virginmedia.com>
2016-10-15 09:57:17 +01:00
2019-01-06 06:29:16 -05:00
pHost->mSslTsl = port_ssl_tsl->isChecked();
if (!character_password_entry->text().trimmed().isEmpty()) {
pHost->setPass(character_password_entry->text().trimmed());
2011-07-05 00:07:41 +02:00
}
// Note: If password field is empty, we don't call slot_updatePassword() because:
// 1. The host's password was already loaded via Host::loadSecuredPassword()
// 2. Calling slot_updatePassword with empty password would delete the stored password
// 3. slot_updatePassword reads profile from list widget which could mismatch
Update: remove Midkemia (and add WoTMUD) to list of predefined MUDs (#321) * Update: remove Midkemia (and add WoTMUD) to list of predefined MUDs The I.R.E. MUD Midkemia-online was shutdown on 2016/09/07 and it is not helpful to continue to offer it as a predefined MUD in the connection dialog. This commit removes it from that list and adds a favourite of mine "WoTMUD" to replace it. The logo is a temporary one that I will replace with a similar one that I need to get clearance to use (and release with a GPL2.1+ licence) from the Wheel of Time MUD sysop with whom I am in contact {Vivienne needs to get approval from the creator Flash who is away for a few days...!} The dlgConnectionProfiles.cpp file has quite a few ASCII strings, as appropriate for a file that will in the future be subject to I18n work I took the opportunity to put QStringLiteral(...) wrappers around the QStrings that are used for non-GUI tasks and tr(...) ones that are. I expect it isn't complete but it goes someway towards dealing with THIS particular file - and it should improve very slightly the generation of QStrings that need to be constructed because it means as much of possible of them is done at compile rather than run-time...! In relation to the above there are points in the code where QStrings are compared to "" to see if they do not have any contents - the isEmpty() method is a better way to do the same (it does not involve a string expression) In (void)dlgConnectionProfiles::slot_update_name( const QString ) there are a couple of variables that use a double underscore prefix - that is NOT A Good Idea: such a prefix is RESERVED in both C and C++. For example see: [what-are-the-rules-about-using-an-underscore-in-a-c-identifier](http://stackoverflow.com/questions/228783/what-are-the-rules-about-using-an-underscore-in-a-c-identifier) also a single prefix might be a bit of a problem with libraries... I have renames the offending things there - but I may have used the single underscore prefix myself in the past. *blush* There are some help messages in this class that use an apostrophe in a word contraction - as a matter of readability this is not considered best practice IMHO so I have converted them to the full words. Following experiences in my previous Pull Request I also took a look at the initialisation and use of some member variables; it became clear that: * (QString) dlgConnectionProfiles::mOrigin * (bool) dlgConnectionProfiles::mEditOK were not used and could be simply deleted and that: * (QString) dlgConnectionProfiles::mUnsavedProfileName * (QString) dlgConnectionProfiles::mCurrentProfileEditName did not need to be member variables and could be local to the method that used them, so in (void)dlgConnectionProfiles::slot_save_name(): * (QString) dlgConnectionProfiles::mCurrentProfileEditName becomes local: (QString) currentProfileEditName and in (void) dlgConnectionProfiles::slot_addProfile(): * (QString) dlgConnectionProfiles::mUnsavedProfileName becomes local: (QString) newName also added initialisers for: * (QStringList) dlgConnectionProfiles::mProfileList * (QPushButton *) dlgConnectionProfiles::connect_button * (QLineEditQPushButton *) dlgConnectionProfiles::delete_profile_lineedit * (QPushButton *) dlgConnectionProfiles::delete_button Signed-off-by: Stephen Lyons <slysven@virginmedia.com> * More: two items missed from prior commit In the previous commit I forgot to take out: * a check for Midkemia in the dlgProfilePreferences class constructor that enabled the download map options; * a similar check in (bool)TMap::restore(QString) that asked if the user wanted to download a map if there was not one found. Signed-off-by: Stephen Lyons <slysven@virginmedia.com> * Tweak: Insert MUD supplied Logo/Icon Received an icon as a .jpg that the MUD operators can allow us to licence as GPL2.1+ which I have converted to a .png file. Signed-off-by: Stephen Lyons <slysven@virginmedia.com>
2016-10-15 09:57:17 +01:00
if (!login_entry->text().trimmed().isEmpty()) {
pHost->setLogin(login_entry->text().trimmed());
} else {
Infrastructure: tidy up naming of SLOT methods and their usage - Part 2 (#6255) When Qt's slot/signal system is used to invoke a method there is some overhead - so it makes sense to ensure developers can spot all such methods (functions). We have tried to do this with a `slot_` prefix to the methods we create but it has not been applied uniformly. This PR (and one or more to follow) is intended to help with this by more rigorously doing so - the names changed herein should all follow a `slot_`*camelCaseMethodName* style. In addition some methods were detected that are not currently used or which are not actually used via the signal/slot system, these have been commented out or have had the prefix removed and the declaration in the relevant header file moved as appropriate. For reference the changes made are: * `TLuaInterpreter::slotDeleteSender(...)` ==> `TLuaInterpreter::slot_deleteSender(...)` * `TLuaInterpreter::slotPurge()` ==> `TLuaInterpreter::slot_purge()` * `cTelnet::handle_socket_signal_connected()` ==> `cTelnet::slot_socketConnected()` * `cTelnet::handle_socket_signal_disconnected()` ==> `cTelnet::slot_socketDisconnected()` * `cTelnet::handle_socket_signal_hostFound(...)` ==> `cTelnet::slot_socketHostFound(...)` * `cTelnet::handle_socket_signal_readyRead()` ==> `cTelnet::slot_socketReadyToBeRead()` * `cTelnet::handle_socket_signal_sslError(...)` ==> `cTelnet::slot_socketSslError(...)` * `cTelnet::setDownloadProgress(...)` ==> `cTelnet::slot_setDownloadProgress(...)` * `dlgComposer::cancel()` ==> `dlgComposer::slot_cancel()` * `dlgComposer::save()` ==> `dlgComposer::slot_save()` * `dlgConnectionProfiles::slot_item_clicked(...)` ==> `dlgConnectionProfiles::slot_itemClicked(...)` * `dlgConnectionProfiles::slot_save_name()` ==> `dlgConnectionProfiles::slot_saveName()` * `dlgConnectionProfiles::slot_update_login(...)` ==> `dlgConnectionProfiles::slot_updateLogin(...)` * `dlgConnectionProfiles::slot_update_name(...)` ==> `dlgConnectionProfiles::slot_updateName(...)` * `dlgConnectionProfiles::slot_update_pass(...)` ==> `dlgConnectionProfiles::slot_updatePassword(...)` * `dlgConnectionProfiles::slot_update_port(...)` ==> `dlgConnectionProfiles::slot_updatePort(...)` * `dlgConnectionProfiles::slot_update_SSL_TSL_port(...)` ==> `dlgConnectionProfiles::slot_updateSslTslPort(...)` * `dlgConnectionProfiles::slot_update_url(...)` ==> `dlgConnectionProfiles::slot_updateUrl(...)` * `dlgMapLabel::pickBgColor()` ==> `dlgMapLabel::slot_pickBgColor()` * `dlgMapLabel::pickFgColor()` ==> `dlgMapLabel::slot_pickFgColor()` * `dlgMapLabel::pickFile()` ==> `dlgMapLabel::slot_pickFile()` * `dlgMapLabel::pickFont()` ==> `dlgMapLabel::slot_pickFont()` * `dlgMapLabel::save()` ==> `dlgMapLabel::slot_save()` * `dlgMapLabel::updateControls()` ==> `dlgMapLabel::slot_updateControls()` * `dlgMapLabel::updateControlsVisibility()` ==> `dlgMapLabel::slot_updateControlsVisibility()` Commented out as not being used: * `cTelnet::handle_socket_signal_error()` ==> `cTelnet::slot_socketError()` * `dlgConnectionProfiles::slot_update_website(...) ==> `dlgConnectionProfiles::slot_updateWebsite(const QString& url)` I have my doubts about whether the `dlgMapLabel::save()`/`dlgMapLabel::slot_save()` method is needed at all, as it might be that the `QDialog::accept()` slot could have been inserted into the `QObject::connect(...)` call directly instead...? Signed-off-by: Stephen Lyons <slysven@virginmedia.com>
2022-08-27 10:47:01 +02:00
slot_updateLogin(pHost->getLogin());
}
// This settings also need to be configured, note that the only time not to
// save the setting is on profile loading. Only override the default UTF-8
// encoding if a saved encoding exists:
const QByteArray savedEncoding = readProfileData(profile_name, qsl("encoding")).toUtf8();
if (!savedEncoding.isEmpty()) {
pHost->mTelnet.setEncoding(savedEncoding, false);
}
// Needed to ensure setting is correct on start-up:
pHost->setWideAmbiguousEAsianGlyphs(pHost->getWideAmbiguousEAsianGlyphsControlState());
2019-01-06 06:29:16 -05:00
pHost->setAutoReconnect(auto_reconnect->isChecked());
2018-10-05 06:25:57 +02:00
// This also writes the value out to the profile's base directory:
mudlet::self()->mDiscord.setApplicationID(pHost, mDiscordApplicationId);
}
emit signal_load_profile(profile_name, alsoConnect);
QDialog::accept();
2009-01-24 02:50:22 +01:00
}
2019-01-06 06:29:16 -05:00
bool dlgConnectionProfiles::validateProfile()
{
2019-01-06 06:29:16 -05:00
bool valid = true;
// don't validate url duplication during copy, as information will already exist when we try to set it
if (mCopyingProfile) {
return true;
}
validName = true, validPort = true, validUrl = true;
2021-02-03 19:59:35 +00:00
clearNotificationArea();
2019-01-06 06:29:16 -05:00
QListWidgetItem* pItem = listWidget_profiles->currentItem();
2019-01-06 06:29:16 -05:00
if (pItem) {
QString name = profile_name_entry->text().trimmed();
// Only check the characters of a new or edited name: a profile folder
// already on disk may have been created outside of Mudlet (e.g. by a
// file manager copying a folder) with characters we would not permit
// for a new name - such a profile must still be loadable. Comparing
// against the trimmed item name covers folders with leading/trailing
// whitespace too, as the entered name always arrives trimmed. Names
// the rest of Mudlet cannot work with get no exemption: renaming them
// is worse for the user than a profile whose password never saves.
// "." and ".." name something that exists without being a profile, so
// the exemption needs a folder that is genuinely the profile's own.
const QString selectedName = pItem->data(csmNameRole).toString();
const QString selectedFolder = profileFolderPath(mudlet::getMudletPath(enums::profilesPath), selectedName);
const bool nameIsFolderOnDisk = (name == selectedName.trimmed()) && !selectedFolder.isEmpty() && QDir(selectedFolder).exists();
const bool nameUnchangedAndOnDisk = nameIsFolderOnDisk && profileNameUsableAsIs(name);
const QChar invalidChar = nameUnchangedAndOnDisk ? QChar() : firstInvalidProfileNameChar(name);
if (!invalidChar.isNull()) {
notificationAreaIconLabelWarning->show();
notificationAreaMessageBox->setText(
qsl("%1\n%2\n%3\n").arg(notificationAreaMessageBox->text(), tr("The %1 character is not permitted. Use one of the following:").arg(invalidChar), scmAllowedProfileNameChars));
name.remove(invalidChar);
profile_name_entry->setText(name);
validName = false;
valid = false;
} else if (!nameIsFolderOnDisk && !name.isEmpty() && !profileNameUsableAsIs(name)) {
// Nothing to strip here, unlike the branch above: the characters
// are all permitted, it is the whole name that cannot be a folder
notificationAreaIconLabelWarning->show();
notificationAreaMessageBox->setText(
qsl("%1\n%2\n")
.arg(notificationAreaMessageBox->text(),
//: Shown when a profile name would not name a folder of its own. Keep the quoted dots as they are, they are literal characters the user typed
tr("A profile name cannot be \".\" or contain \"..\", as those refer to other folders on your computer. Please pick a different name.")));
validName = false;
valid = false;
2019-01-06 06:29:16 -05:00
}
// see if there is an edit that already uses a similar name
if ((QString::compare(pItem->data(csmNameRole).toString(), name, Qt::CaseInsensitive) != 0) && mProfileList.contains(name, Qt::CaseInsensitive)) {
2019-01-06 06:29:16 -05:00
notificationAreaIconLabelError->show();
notificationAreaMessageBox->setText(qsl("%1\n%2").arg(notificationAreaMessageBox->text(), tr("This profile name is already in use.")));
2019-01-06 06:29:16 -05:00
validName = false;
valid = false;
}
const QString port = port_entry->text().trimmed();
if (!port.isEmpty() && (port.indexOf(QRegularExpression(qsl("^\\d+$")), 0) == -1)) {
2019-01-06 06:29:16 -05:00
QString val = port;
val.chop(1);
port_entry->setText(val);
notificationAreaIconLabelError->show();
notificationAreaMessageBox->setText(qsl("%1\n%2").arg(notificationAreaMessageBox->text(), tr("You have to enter a number. Other characters are not permitted.")));
2019-01-06 06:29:16 -05:00
port_entry->setPalette(mErrorPalette);
validPort = false;
valid = false;
}
bool ok;
const int num = port.trimmed().toInt(&ok);
2019-01-06 06:29:16 -05:00
if (!port.isEmpty() && (num > 65536 && ok)) {
notificationAreaIconLabelError->show();
notificationAreaMessageBox->setText(qsl("%1\n%2\n\n").arg(notificationAreaMessageBox->text(), tr("Port number must be above zero and below 65535.")));
2019-01-06 06:29:16 -05:00
port_entry->setPalette(mErrorPalette);
validPort = false;
valid = false;
}
#if defined(QT_NO_SSL)
port_ssl_tsl->setEnabled(false);
port_ssl_tsl->setToolTip(utils::richText(tr("Mudlet is not configured for secure connections.")));
2019-01-06 06:29:16 -05:00
if (port_ssl_tsl->isChecked()) {
notificationAreaIconLabelError->show();
notificationAreaMessageBox->setText(qsl("%1\n%2\n\n").arg(notificationAreaMessageBox->text(), tr("Mudlet is not configured for secure connections.")));
2019-01-06 06:29:16 -05:00
port_ssl_tsl->setEnabled(true);
validPort = false;
valid = false;
}
#else
if (!QSslSocket::supportsSsl()) {
if (port_ssl_tsl->isChecked()) {
notificationAreaIconLabelError->show();
notificationAreaMessageBox->setText(qsl("%1\n%2\n\n").arg(notificationAreaMessageBox->text(), tr("Mudlet can not load support for secure connections.")));
2019-01-06 06:29:16 -05:00
validPort = false;
valid = false;
}
} else {
port_ssl_tsl->setEnabled(true);
Refactor: remove minute visible name from Connection dialogue icons We have been trying to hide the text associated with the `QListWidgetItems` in the connection dialogue by setting the font size to the minimum of `1` and by setting it's colour to be white. This is not effective when the background is not white - which is likely for a "Dark" desktop environment. The only way to successfully hide the text is, I think, to not have any! However the text was being used programmatically, so the best way to use a `QListWidget` in this mannar is to store the text elsewhere in each item's structure. Fortunately Qt provides for this with the user data functionality which allows multiple data items (based on the `QVariant` class) to be stored within each `QListWidgetItem` using an integer key to denote the type of the data. Ironically the text, icon and other details for each item are ALSO stored in this way - however for non-Qt internal use the lowest integer key that is to be used is `Qt::UserRole` - which I have assigned to the `(const int) dlgConnectionProfiles::csmNameRole` static value. It is quite possible that a redesign of the Connect Profiles dialogue may use this system to store/cache more details about each profile in the future! The only issue with this is the lack of a: `QListWidget::findData(const QVarient&data, int role = Qt::UserRole ...)` method (c.f. `QComboBox::findData(...)`) so I have had to provide a: `(QList<QListWidgetItem*>) findData(const QListWidget&, const QVariant&, const int role = Qt::UserRole) const; method to fill in this gap and to replace the previous `QListWidget::find(...)` that examined each item's text. Also removed local `(QString) profile` from: `(void) dlgConnectionProfiles::slot_item_clicked(QListWidgetItem*)` as it is merely a redundent duplicate of another local `(QString) profile_name`. Renamed local `(QListWidgetItem*) pM` from: `(void) dlgConnectionProfiles::fillout_form()` to: `(QListWidgetItem*) pItem` as that reflects the name used throughout the rest of the class for this type of variable. Changed a `(QLabel*)::setText(tr(""))` call to the more explicit and less stupid `(QLabel*)::clear()`. Similarly changed a `(QWidget*)::setToolTip("")` to a `(QWidget*)::setToolTip(QString())`. Signed-off-by: Stephen Lyons <slysven@virginmedia.com>
2020-07-01 20:59:42 +01:00
port_ssl_tsl->setToolTip(QString());
2019-01-06 06:29:16 -05:00
}
#endif
2019-01-06 06:29:16 -05:00
QUrl check;
const QString url = host_name_entry->text().trimmed();
2019-01-06 06:29:16 -05:00
check.setHost(url);
if (url.isEmpty()) {
host_name_entry->setPalette(mErrorPalette);
validUrl = false;
valid = false;
}
2019-01-06 06:29:16 -05:00
if (!check.isValid()) {
notificationAreaIconLabelError->show();
notificationAreaMessageBox->setText(qsl("%1\n%2\n\n%3").arg(notificationAreaMessageBox->text(), tr("Please enter the URL or IP address of the Game server."), check.errorString()));
2019-01-06 06:29:16 -05:00
host_name_entry->setPalette(mErrorPalette);
validUrl = false;
valid = false;
}
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
// Need to reject raw IP addresses (of either version 4 or 6 type) as
// it is very unlikely that the Security Certificates include them as
// a Host Name.
if (port_ssl_tsl->isChecked() && (cTelnet::isRawIPv4Address(url) || cTelnet::isRawIPv6Address(url))) {
notificationAreaIconLabelError->show();
// As the only tags are not on the first line the default
// Qt::AutoFormat won't detect that rich-text is present in this text!
notificationAreaMessageBox->setTextFormat(Qt::RichText);
/*: Please use two line-feeds after the first line so the second
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
line can be italicised and spaced out - if appropriate for
the locale.*/
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
notificationAreaMessageBox->setText(qsl("%1%2\n\n%3")
.arg(!notificationAreaMessageBox->text().isEmpty() ? notificationAreaMessageBox->text().append(QChar::LineFeed) : QString(),
tr("Please enter the URL of the Game server.\n\n"
"<i>SSL/TLS connections require a URL, as an IP address is not a suitable "
"identifier for the certification of the Game Server.</i>"),
check.errorString()));
host_name_entry->setPalette(mErrorPalette);
validUrl = false;
valid = false;
2019-01-06 06:29:16 -05:00
}
if (valid) {
port_entry->setPalette(mOKPalette);
host_name_entry->setPalette(mOKPalette);
2021-02-03 19:59:35 +00:00
clearNotificationArea();
2019-01-06 06:29:16 -05:00
validName = true;
validPort = true;
validUrl = true;
if (offline_button) {
offline_button->setEnabled(true);
offline_button->setToolTip(utils::richText(tr("Load profile without connecting.")));
offline_button->setAccessibleDescription(btn_load_enabled_accessDesc);
}
2019-01-06 06:29:16 -05:00
if (connect_button) {
connect_button->setEnabled(true);
connect_button->setToolTip(QString());
connect_button->setAccessibleDescription(btn_connect_enabled_accessDesc);
2019-01-06 06:29:16 -05:00
}
return true;
}
if (!notificationAreaMessageBox->text().isEmpty()) {
notificationArea->show();
notificationAreaMessageBox->show();
}
if (offline_button) {
offline_button->setEnabled(false);
offline_button->setToolTip(utils::richText(tr("Please set a valid profile name, game server address and the game port before loading.")));
offline_button->setAccessibleDescription(btn_connOrLoad_disabled_accessDesc);
}
if (connect_button) {
connect_button->setEnabled(false);
connect_button->setToolTip(utils::richText(tr("Please set a valid profile name, game server address and the game port before connecting.")));
connect_button->setAccessibleDescription(btn_connOrLoad_disabled_accessDesc);
}
return false;
2019-01-06 06:29:16 -05:00
}
return false;
}
// credit: http://www.qtcentre.org/archive/index.php/t-23469.html
bool dlgConnectionProfiles::copyFolder(const QString& sourceFolder, const QString& destFolder)
{
const QDir sourceDir(sourceFolder);
if (!sourceDir.exists()) {
return false;
}
const QDir destDir(destFolder);
if (!destDir.exists()) {
destDir.mkdir(destFolder);
}
QStringList files = sourceDir.entryList(QDir::Files);
for (const QString& file : std::as_const(files)) {
const QString srcName = sourceFolder + QDir::separator() + file;
const QString destName = destFolder + QDir::separator() + file;
QFile::copy(srcName, destName);
}
files.clear();
files = sourceDir.entryList(QDir::AllDirs | QDir::NoDotAndDotDot);
for (const QString& file : std::as_const(files)) {
const QString srcName = sourceFolder + QDir::separator() + file;
const QString destName = destFolder + QDir::separator() + file;
copyFolder(srcName, destName);
}
return true;
}
// As it is wired to the triggered() signal it is only called that way when
// the user clicks on the action, and not when setChecked() is used.
void dlgConnectionProfiles::slot_togglePasswordVisibility(const bool showPassword)
{
if (mpAction_revealPassword->isChecked() != showPassword) {
// This will only be reached and needed by a call NOT prompted by the
// user clicking on the icon - i.e. either when a different profile is
// selected or when called from the constructor:
mpAction_revealPassword->setChecked(showPassword);
}
if (mpAction_revealPassword->isChecked()) {
character_password_entry->setEchoMode(QLineEdit::Normal);
// In practice I could not get the icon to change based upon supplying
// different QPixmaps for the QIcon for different states - so let's do it
// directly:
mpAction_revealPassword->setIcon(QIcon::fromTheme(qsl("password-show-on"), QIcon(qsl(":/icons/password-show-on.png"))));
mpAction_revealPassword->setToolTip(utils::richText(tr("Click to hide the password; it will also hide if another profile is selected.")));
} else {
character_password_entry->setEchoMode(QLineEdit::Password);
mpAction_revealPassword->setIcon(QIcon::fromTheme(qsl("password-show-off"), QIcon(qsl(":/icons/password-show-off.png"))));
mpAction_revealPassword->setToolTip(utils::richText(tr("Click to reveal the password for this profile.")));
}
}
Refactor: remove minute visible name from Connection dialogue icons We have been trying to hide the text associated with the `QListWidgetItems` in the connection dialogue by setting the font size to the minimum of `1` and by setting it's colour to be white. This is not effective when the background is not white - which is likely for a "Dark" desktop environment. The only way to successfully hide the text is, I think, to not have any! However the text was being used programmatically, so the best way to use a `QListWidget` in this mannar is to store the text elsewhere in each item's structure. Fortunately Qt provides for this with the user data functionality which allows multiple data items (based on the `QVariant` class) to be stored within each `QListWidgetItem` using an integer key to denote the type of the data. Ironically the text, icon and other details for each item are ALSO stored in this way - however for non-Qt internal use the lowest integer key that is to be used is `Qt::UserRole` - which I have assigned to the `(const int) dlgConnectionProfiles::csmNameRole` static value. It is quite possible that a redesign of the Connect Profiles dialogue may use this system to store/cache more details about each profile in the future! The only issue with this is the lack of a: `QListWidget::findData(const QVarient&data, int role = Qt::UserRole ...)` method (c.f. `QComboBox::findData(...)`) so I have had to provide a: `(QList<QListWidgetItem*>) findData(const QListWidget&, const QVariant&, const int role = Qt::UserRole) const; method to fill in this gap and to replace the previous `QListWidget::find(...)` that examined each item's text. Also removed local `(QString) profile` from: `(void) dlgConnectionProfiles::slot_item_clicked(QListWidgetItem*)` as it is merely a redundent duplicate of another local `(QString) profile_name`. Renamed local `(QListWidgetItem*) pM` from: `(void) dlgConnectionProfiles::fillout_form()` to: `(QListWidgetItem*) pItem` as that reflects the name used throughout the rest of the class for this type of variable. Changed a `(QLabel*)::setText(tr(""))` call to the more explicit and less stupid `(QLabel*)::clear()`. Similarly changed a `(QWidget*)::setToolTip("")` to a `(QWidget*)::setToolTip(QString())`. Signed-off-by: Stephen Lyons <slysven@virginmedia.com>
2020-07-01 20:59:42 +01:00
QList<QListWidgetItem*> dlgConnectionProfiles::findData(const QListWidget& listWidget, const QVariant& what, const int role) const
{
QList<QListWidgetItem*> results;
for (int index = 0, total = listWidget.count(); index < total; ++index) {
if (listWidget.item(index)->data(role) == what) {
results.append(listWidget.item(index));
}
}
return results;
}
Revise: select profiles by typing the name, keep profile icons (#4440) Restore the text display of the profile name. This is so that the keyboard selection of profiles beginning with a particular letter works again. This will close issue #4283 . This also sets it to the minimum size trying to hide it by setting the font size to 1 - however that still show something (and a font size of zero is NOT valid). Manipulating the QPalette of the QListWidget does not work well enough in the presence of Styles or StyleSheets to be a viable means of hiding the text either. Also in the `dlgConnection` class: * refactor out some highly repetitive code that sets up the predefined Mud icons. * remove warnings and adopts a change introduced into Qt 5.15 for the QLabel::pixmap() which now requires a Qt::ReturnByValue argument to prevent an obsolete code warning and to force a QPixmap to be returned by value and not reference... * remove a warning about: QListWidget::setItemSelected(QListWidgetItem*,bool) being deprecated and switches to the recommended: QListWidgetItem::setSelected(bool) Edit: the last item above may have become redundant as further commits amalgamated into this PR may have taken out these lines anyhow! Change the custom icon generator so that there is NOT a dependency on the position in the list for the colours used. This means that the same name will always have the same colours - which will help the user to pick out a particular profile as it will always look the same no matter if other profiles are added or removed. Remove the colour gradient in the custom icon as it is not thought to be useful nowadays. Refactor out the colour icon generator to a single method that is called from more than one place. BugFix: restore odd original way of accessing some QPixmaps This was causing failures in some CI platforms using Qt 5.14. or older. Revise: really frob the custom icon generator This is to try and produce as varied but still readable icons when they are generated entirely on the basis of the MUD name - in a reproducible (though Qt do NOT guarantee repeatable qHash() functionality across Qt versions - so they may not stay *that* constant) manner. Refrob: use a table of fixed colours Using only 15 hues but with 3 different saturation level plus 5 grey-scale one should give enough different icons but not generate them too close to each other for those with normal colour vision... Signed-off-by: Stephen Lyons <slysven@virginmedia.com> Co-authored-by: Kebap <kebap_spam@gmx.net>
2020-12-11 17:53:28 +00:00
2021-02-03 19:59:35 +00:00
QList<int> dlgConnectionProfiles::findProfilesBeginningWith(const QString& what) const
{
QList<int> results;
for (int index = 0, total = listWidget_profiles->count(); index < total; ++index) {
if (listWidget_profiles->item(index)->data(csmNameRole).toString().startsWith(what, Qt::CaseInsensitive)) {
2021-02-03 19:59:35 +00:00
results.append(index);
}
}
return results;
}
Revise: select profiles by typing the name, keep profile icons (#4440) Restore the text display of the profile name. This is so that the keyboard selection of profiles beginning with a particular letter works again. This will close issue #4283 . This also sets it to the minimum size trying to hide it by setting the font size to 1 - however that still show something (and a font size of zero is NOT valid). Manipulating the QPalette of the QListWidget does not work well enough in the presence of Styles or StyleSheets to be a viable means of hiding the text either. Also in the `dlgConnection` class: * refactor out some highly repetitive code that sets up the predefined Mud icons. * remove warnings and adopts a change introduced into Qt 5.15 for the QLabel::pixmap() which now requires a Qt::ReturnByValue argument to prevent an obsolete code warning and to force a QPixmap to be returned by value and not reference... * remove a warning about: QListWidget::setItemSelected(QListWidgetItem*,bool) being deprecated and switches to the recommended: QListWidgetItem::setSelected(bool) Edit: the last item above may have become redundant as further commits amalgamated into this PR may have taken out these lines anyhow! Change the custom icon generator so that there is NOT a dependency on the position in the list for the colours used. This means that the same name will always have the same colours - which will help the user to pick out a particular profile as it will always look the same no matter if other profiles are added or removed. Remove the colour gradient in the custom icon as it is not thought to be useful nowadays. Refactor out the colour icon generator to a single method that is called from more than one place. BugFix: restore odd original way of accessing some QPixmaps This was causing failures in some CI platforms using Qt 5.14. or older. Revise: really frob the custom icon generator This is to try and produce as varied but still readable icons when they are generated entirely on the basis of the MUD name - in a reproducible (though Qt do NOT guarantee repeatable qHash() functionality across Qt versions - so they may not stay *that* constant) manner. Refrob: use a table of fixed colours Using only 15 hues but with 3 different saturation level plus 5 grey-scale one should give enough different icons but not generate them too close to each other for those with normal colour vision... Signed-off-by: Stephen Lyons <slysven@virginmedia.com> Co-authored-by: Kebap <kebap_spam@gmx.net>
2020-12-11 17:53:28 +00:00
void dlgConnectionProfiles::setItemName(QListWidgetItem* pI, const QString& name) const
{
if (!pI) {
// Avoid any problems should the supplied argument be a nullptr:
return;
}
pI->setData(csmNameRole, name);
pI->setData(Qt::AccessibleTextRole, item_profile_accessName.arg(name));
pI->setData(Qt::AccessibleDescriptionRole, item_profile_accessDesc);
}
void dlgConnectionProfiles::setupMudProfile(QListWidgetItem* pItem, const QString& mudServer, const QString& serverDescription, const QString& iconFileName)
{
setItemName(pItem, mudServer);
listWidget_profiles->addItem(pItem);
Revise: select profiles by typing the name, keep profile icons (#4440) Restore the text display of the profile name. This is so that the keyboard selection of profiles beginning with a particular letter works again. This will close issue #4283 . This also sets it to the minimum size trying to hide it by setting the font size to 1 - however that still show something (and a font size of zero is NOT valid). Manipulating the QPalette of the QListWidget does not work well enough in the presence of Styles or StyleSheets to be a viable means of hiding the text either. Also in the `dlgConnection` class: * refactor out some highly repetitive code that sets up the predefined Mud icons. * remove warnings and adopts a change introduced into Qt 5.15 for the QLabel::pixmap() which now requires a Qt::ReturnByValue argument to prevent an obsolete code warning and to force a QPixmap to be returned by value and not reference... * remove a warning about: QListWidget::setItemSelected(QListWidgetItem*,bool) being deprecated and switches to the recommended: QListWidgetItem::setSelected(bool) Edit: the last item above may have become redundant as further commits amalgamated into this PR may have taken out these lines anyhow! Change the custom icon generator so that there is NOT a dependency on the position in the list for the colours used. This means that the same name will always have the same colours - which will help the user to pick out a particular profile as it will always look the same no matter if other profiles are added or removed. Remove the colour gradient in the custom icon as it is not thought to be useful nowadays. Refactor out the colour icon generator to a single method that is called from more than one place. BugFix: restore odd original way of accessing some QPixmaps This was causing failures in some CI platforms using Qt 5.14. or older. Revise: really frob the custom icon generator This is to try and produce as varied but still readable icons when they are generated entirely on the basis of the MUD name - in a reproducible (though Qt do NOT guarantee repeatable qHash() functionality across Qt versions - so they may not stay *that* constant) manner. Refrob: use a table of fixed colours Using only 15 hues but with 3 different saturation level plus 5 grey-scale one should give enough different icons but not generate them too close to each other for those with normal colour vision... Signed-off-by: Stephen Lyons <slysven@virginmedia.com> Co-authored-by: Kebap <kebap_spam@gmx.net>
2020-12-11 17:53:28 +00:00
if (!hasCustomIcon(mudServer)) {
const QPixmap pixmap(iconFileName);
if (pixmap.isNull()) {
qWarning() << mudServer << "doesn't have a valid icon";
return;
}
if (pixmap.width() != 120) {
pItem->setIcon(pixmap.scaled(QSize(120, 30), Qt::IgnoreAspectRatio, Qt::SmoothTransformation));
Revise: select profiles by typing the name, keep profile icons (#4440) Restore the text display of the profile name. This is so that the keyboard selection of profiles beginning with a particular letter works again. This will close issue #4283 . This also sets it to the minimum size trying to hide it by setting the font size to 1 - however that still show something (and a font size of zero is NOT valid). Manipulating the QPalette of the QListWidget does not work well enough in the presence of Styles or StyleSheets to be a viable means of hiding the text either. Also in the `dlgConnection` class: * refactor out some highly repetitive code that sets up the predefined Mud icons. * remove warnings and adopts a change introduced into Qt 5.15 for the QLabel::pixmap() which now requires a Qt::ReturnByValue argument to prevent an obsolete code warning and to force a QPixmap to be returned by value and not reference... * remove a warning about: QListWidget::setItemSelected(QListWidgetItem*,bool) being deprecated and switches to the recommended: QListWidgetItem::setSelected(bool) Edit: the last item above may have become redundant as further commits amalgamated into this PR may have taken out these lines anyhow! Change the custom icon generator so that there is NOT a dependency on the position in the list for the colours used. This means that the same name will always have the same colours - which will help the user to pick out a particular profile as it will always look the same no matter if other profiles are added or removed. Remove the colour gradient in the custom icon as it is not thought to be useful nowadays. Refactor out the colour icon generator to a single method that is called from more than one place. BugFix: restore odd original way of accessing some QPixmaps This was causing failures in some CI platforms using Qt 5.14. or older. Revise: really frob the custom icon generator This is to try and produce as varied but still readable icons when they are generated entirely on the basis of the MUD name - in a reproducible (though Qt do NOT guarantee repeatable qHash() functionality across Qt versions - so they may not stay *that* constant) manner. Refrob: use a table of fixed colours Using only 15 hues but with 3 different saturation level plus 5 grey-scale one should give enough different icons but not generate them too close to each other for those with normal colour vision... Signed-off-by: Stephen Lyons <slysven@virginmedia.com> Co-authored-by: Kebap <kebap_spam@gmx.net>
2020-12-11 17:53:28 +00:00
} else {
pItem->setIcon(QIcon(iconFileName));
}
} else {
setCustomIcon(mudServer, pItem);
}
if (!serverDescription.isEmpty()) {
pItem->setToolTip(utils::richText(serverDescription));
Revise: select profiles by typing the name, keep profile icons (#4440) Restore the text display of the profile name. This is so that the keyboard selection of profiles beginning with a particular letter works again. This will close issue #4283 . This also sets it to the minimum size trying to hide it by setting the font size to 1 - however that still show something (and a font size of zero is NOT valid). Manipulating the QPalette of the QListWidget does not work well enough in the presence of Styles or StyleSheets to be a viable means of hiding the text either. Also in the `dlgConnection` class: * refactor out some highly repetitive code that sets up the predefined Mud icons. * remove warnings and adopts a change introduced into Qt 5.15 for the QLabel::pixmap() which now requires a Qt::ReturnByValue argument to prevent an obsolete code warning and to force a QPixmap to be returned by value and not reference... * remove a warning about: QListWidget::setItemSelected(QListWidgetItem*,bool) being deprecated and switches to the recommended: QListWidgetItem::setSelected(bool) Edit: the last item above may have become redundant as further commits amalgamated into this PR may have taken out these lines anyhow! Change the custom icon generator so that there is NOT a dependency on the position in the list for the colours used. This means that the same name will always have the same colours - which will help the user to pick out a particular profile as it will always look the same no matter if other profiles are added or removed. Remove the colour gradient in the custom icon as it is not thought to be useful nowadays. Refactor out the colour icon generator to a single method that is called from more than one place. BugFix: restore odd original way of accessing some QPixmaps This was causing failures in some CI platforms using Qt 5.14. or older. Revise: really frob the custom icon generator This is to try and produce as varied but still readable icons when they are generated entirely on the basis of the MUD name - in a reproducible (though Qt do NOT guarantee repeatable qHash() functionality across Qt versions - so they may not stay *that* constant) manner. Refrob: use a table of fixed colours Using only 15 hues but with 3 different saturation level plus 5 grey-scale one should give enough different icons but not generate them too close to each other for those with normal colour vision... Signed-off-by: Stephen Lyons <slysven@virginmedia.com> Co-authored-by: Kebap <kebap_spam@gmx.net>
2020-12-11 17:53:28 +00:00
}
}
QIcon dlgConnectionProfiles::customIcon(const QString& text, const std::optional<QColor>& backgroundColor) const
Revise: select profiles by typing the name, keep profile icons (#4440) Restore the text display of the profile name. This is so that the keyboard selection of profiles beginning with a particular letter works again. This will close issue #4283 . This also sets it to the minimum size trying to hide it by setting the font size to 1 - however that still show something (and a font size of zero is NOT valid). Manipulating the QPalette of the QListWidget does not work well enough in the presence of Styles or StyleSheets to be a viable means of hiding the text either. Also in the `dlgConnection` class: * refactor out some highly repetitive code that sets up the predefined Mud icons. * remove warnings and adopts a change introduced into Qt 5.15 for the QLabel::pixmap() which now requires a Qt::ReturnByValue argument to prevent an obsolete code warning and to force a QPixmap to be returned by value and not reference... * remove a warning about: QListWidget::setItemSelected(QListWidgetItem*,bool) being deprecated and switches to the recommended: QListWidgetItem::setSelected(bool) Edit: the last item above may have become redundant as further commits amalgamated into this PR may have taken out these lines anyhow! Change the custom icon generator so that there is NOT a dependency on the position in the list for the colours used. This means that the same name will always have the same colours - which will help the user to pick out a particular profile as it will always look the same no matter if other profiles are added or removed. Remove the colour gradient in the custom icon as it is not thought to be useful nowadays. Refactor out the colour icon generator to a single method that is called from more than one place. BugFix: restore odd original way of accessing some QPixmaps This was causing failures in some CI platforms using Qt 5.14. or older. Revise: really frob the custom icon generator This is to try and produce as varied but still readable icons when they are generated entirely on the basis of the MUD name - in a reproducible (though Qt do NOT guarantee repeatable qHash() functionality across Qt versions - so they may not stay *that* constant) manner. Refrob: use a table of fixed colours Using only 15 hues but with 3 different saturation level plus 5 grey-scale one should give enough different icons but not generate them too close to each other for those with normal colour vision... Signed-off-by: Stephen Lyons <slysven@virginmedia.com> Co-authored-by: Kebap <kebap_spam@gmx.net>
2020-12-11 17:53:28 +00:00
{
QPixmap background(120, 30);
const QColor color = backgroundColor.value_or(mCustomIconColors.at(static_cast<int>((qHash(text) * 8131) % mCustomIconColors.count())));
background.fill(color);
Revise: select profiles by typing the name, keep profile icons (#4440) Restore the text display of the profile name. This is so that the keyboard selection of profiles beginning with a particular letter works again. This will close issue #4283 . This also sets it to the minimum size trying to hide it by setting the font size to 1 - however that still show something (and a font size of zero is NOT valid). Manipulating the QPalette of the QListWidget does not work well enough in the presence of Styles or StyleSheets to be a viable means of hiding the text either. Also in the `dlgConnection` class: * refactor out some highly repetitive code that sets up the predefined Mud icons. * remove warnings and adopts a change introduced into Qt 5.15 for the QLabel::pixmap() which now requires a Qt::ReturnByValue argument to prevent an obsolete code warning and to force a QPixmap to be returned by value and not reference... * remove a warning about: QListWidget::setItemSelected(QListWidgetItem*,bool) being deprecated and switches to the recommended: QListWidgetItem::setSelected(bool) Edit: the last item above may have become redundant as further commits amalgamated into this PR may have taken out these lines anyhow! Change the custom icon generator so that there is NOT a dependency on the position in the list for the colours used. This means that the same name will always have the same colours - which will help the user to pick out a particular profile as it will always look the same no matter if other profiles are added or removed. Remove the colour gradient in the custom icon as it is not thought to be useful nowadays. Refactor out the colour icon generator to a single method that is called from more than one place. BugFix: restore odd original way of accessing some QPixmaps This was causing failures in some CI platforms using Qt 5.14. or older. Revise: really frob the custom icon generator This is to try and produce as varied but still readable icons when they are generated entirely on the basis of the MUD name - in a reproducible (though Qt do NOT guarantee repeatable qHash() functionality across Qt versions - so they may not stay *that* constant) manner. Refrob: use a table of fixed colours Using only 15 hues but with 3 different saturation level plus 5 grey-scale one should give enough different icons but not generate them too close to each other for those with normal colour vision... Signed-off-by: Stephen Lyons <slysven@virginmedia.com> Co-authored-by: Kebap <kebap_spam@gmx.net>
2020-12-11 17:53:28 +00:00
// Set to one larger than wanted so that do loop can contain the decrementor
int fontSize = 30;
QFont font(qsl("Bitstream Vera Sans Mono"), fontSize, QFont::Normal);
Revise: select profiles by typing the name, keep profile icons (#4440) Restore the text display of the profile name. This is so that the keyboard selection of profiles beginning with a particular letter works again. This will close issue #4283 . This also sets it to the minimum size trying to hide it by setting the font size to 1 - however that still show something (and a font size of zero is NOT valid). Manipulating the QPalette of the QListWidget does not work well enough in the presence of Styles or StyleSheets to be a viable means of hiding the text either. Also in the `dlgConnection` class: * refactor out some highly repetitive code that sets up the predefined Mud icons. * remove warnings and adopts a change introduced into Qt 5.15 for the QLabel::pixmap() which now requires a Qt::ReturnByValue argument to prevent an obsolete code warning and to force a QPixmap to be returned by value and not reference... * remove a warning about: QListWidget::setItemSelected(QListWidgetItem*,bool) being deprecated and switches to the recommended: QListWidgetItem::setSelected(bool) Edit: the last item above may have become redundant as further commits amalgamated into this PR may have taken out these lines anyhow! Change the custom icon generator so that there is NOT a dependency on the position in the list for the colours used. This means that the same name will always have the same colours - which will help the user to pick out a particular profile as it will always look the same no matter if other profiles are added or removed. Remove the colour gradient in the custom icon as it is not thought to be useful nowadays. Refactor out the colour icon generator to a single method that is called from more than one place. BugFix: restore odd original way of accessing some QPixmaps This was causing failures in some CI platforms using Qt 5.14. or older. Revise: really frob the custom icon generator This is to try and produce as varied but still readable icons when they are generated entirely on the basis of the MUD name - in a reproducible (though Qt do NOT guarantee repeatable qHash() functionality across Qt versions - so they may not stay *that* constant) manner. Refrob: use a table of fixed colours Using only 15 hues but with 3 different saturation level plus 5 grey-scale one should give enough different icons but not generate them too close to each other for those with normal colour vision... Signed-off-by: Stephen Lyons <slysven@virginmedia.com> Co-authored-by: Kebap <kebap_spam@gmx.net>
2020-12-11 17:53:28 +00:00
// For an icon of size 120x30 allow 89x29 for the text:
const QRect textRectangle(0, 0, 89, 29);
Revise: select profiles by typing the name, keep profile icons (#4440) Restore the text display of the profile name. This is so that the keyboard selection of profiles beginning with a particular letter works again. This will close issue #4283 . This also sets it to the minimum size trying to hide it by setting the font size to 1 - however that still show something (and a font size of zero is NOT valid). Manipulating the QPalette of the QListWidget does not work well enough in the presence of Styles or StyleSheets to be a viable means of hiding the text either. Also in the `dlgConnection` class: * refactor out some highly repetitive code that sets up the predefined Mud icons. * remove warnings and adopts a change introduced into Qt 5.15 for the QLabel::pixmap() which now requires a Qt::ReturnByValue argument to prevent an obsolete code warning and to force a QPixmap to be returned by value and not reference... * remove a warning about: QListWidget::setItemSelected(QListWidgetItem*,bool) being deprecated and switches to the recommended: QListWidgetItem::setSelected(bool) Edit: the last item above may have become redundant as further commits amalgamated into this PR may have taken out these lines anyhow! Change the custom icon generator so that there is NOT a dependency on the position in the list for the colours used. This means that the same name will always have the same colours - which will help the user to pick out a particular profile as it will always look the same no matter if other profiles are added or removed. Remove the colour gradient in the custom icon as it is not thought to be useful nowadays. Refactor out the colour icon generator to a single method that is called from more than one place. BugFix: restore odd original way of accessing some QPixmaps This was causing failures in some CI platforms using Qt 5.14. or older. Revise: really frob the custom icon generator This is to try and produce as varied but still readable icons when they are generated entirely on the basis of the MUD name - in a reproducible (though Qt do NOT guarantee repeatable qHash() functionality across Qt versions - so they may not stay *that* constant) manner. Refrob: use a table of fixed colours Using only 15 hues but with 3 different saturation level plus 5 grey-scale one should give enough different icons but not generate them too close to each other for those with normal colour vision... Signed-off-by: Stephen Lyons <slysven@virginmedia.com> Co-authored-by: Kebap <kebap_spam@gmx.net>
2020-12-11 17:53:28 +00:00
QRect testRect;
// Really long names will be drawn very small (font size 6) with the ends clipped off:
do {
font.setPointSize(--fontSize);
const QFontMetrics metrics(font);
testRect = metrics.boundingRect(textRectangle, Qt::AlignCenter | Qt::TextWordWrap, text);
Revise: select profiles by typing the name, keep profile icons (#4440) Restore the text display of the profile name. This is so that the keyboard selection of profiles beginning with a particular letter works again. This will close issue #4283 . This also sets it to the minimum size trying to hide it by setting the font size to 1 - however that still show something (and a font size of zero is NOT valid). Manipulating the QPalette of the QListWidget does not work well enough in the presence of Styles or StyleSheets to be a viable means of hiding the text either. Also in the `dlgConnection` class: * refactor out some highly repetitive code that sets up the predefined Mud icons. * remove warnings and adopts a change introduced into Qt 5.15 for the QLabel::pixmap() which now requires a Qt::ReturnByValue argument to prevent an obsolete code warning and to force a QPixmap to be returned by value and not reference... * remove a warning about: QListWidget::setItemSelected(QListWidgetItem*,bool) being deprecated and switches to the recommended: QListWidgetItem::setSelected(bool) Edit: the last item above may have become redundant as further commits amalgamated into this PR may have taken out these lines anyhow! Change the custom icon generator so that there is NOT a dependency on the position in the list for the colours used. This means that the same name will always have the same colours - which will help the user to pick out a particular profile as it will always look the same no matter if other profiles are added or removed. Remove the colour gradient in the custom icon as it is not thought to be useful nowadays. Refactor out the colour icon generator to a single method that is called from more than one place. BugFix: restore odd original way of accessing some QPixmaps This was causing failures in some CI platforms using Qt 5.14. or older. Revise: really frob the custom icon generator This is to try and produce as varied but still readable icons when they are generated entirely on the basis of the MUD name - in a reproducible (though Qt do NOT guarantee repeatable qHash() functionality across Qt versions - so they may not stay *that* constant) manner. Refrob: use a table of fixed colours Using only 15 hues but with 3 different saturation level plus 5 grey-scale one should give enough different icons but not generate them too close to each other for those with normal colour vision... Signed-off-by: Stephen Lyons <slysven@virginmedia.com> Co-authored-by: Kebap <kebap_spam@gmx.net>
2020-12-11 17:53:28 +00:00
} while (fontSize > 6 && !textRectangle.contains(testRect));
{ // Enclosed in braces to limit lifespan of QPainter:
QPainter painter(&background);
painter.setCompositionMode(QPainter::CompositionMode_SourceOver);
const QPixmap pixmap(qsl(":/icons/mudlet_main_32px.png"));
painter.drawPixmap(QRect(5, 5, 20, 20), pixmap);
if (color.lightness() > 127) {
painter.setPen(Qt::black);
Revise: select profiles by typing the name, keep profile icons (#4440) Restore the text display of the profile name. This is so that the keyboard selection of profiles beginning with a particular letter works again. This will close issue #4283 . This also sets it to the minimum size trying to hide it by setting the font size to 1 - however that still show something (and a font size of zero is NOT valid). Manipulating the QPalette of the QListWidget does not work well enough in the presence of Styles or StyleSheets to be a viable means of hiding the text either. Also in the `dlgConnection` class: * refactor out some highly repetitive code that sets up the predefined Mud icons. * remove warnings and adopts a change introduced into Qt 5.15 for the QLabel::pixmap() which now requires a Qt::ReturnByValue argument to prevent an obsolete code warning and to force a QPixmap to be returned by value and not reference... * remove a warning about: QListWidget::setItemSelected(QListWidgetItem*,bool) being deprecated and switches to the recommended: QListWidgetItem::setSelected(bool) Edit: the last item above may have become redundant as further commits amalgamated into this PR may have taken out these lines anyhow! Change the custom icon generator so that there is NOT a dependency on the position in the list for the colours used. This means that the same name will always have the same colours - which will help the user to pick out a particular profile as it will always look the same no matter if other profiles are added or removed. Remove the colour gradient in the custom icon as it is not thought to be useful nowadays. Refactor out the colour icon generator to a single method that is called from more than one place. BugFix: restore odd original way of accessing some QPixmaps This was causing failures in some CI platforms using Qt 5.14. or older. Revise: really frob the custom icon generator This is to try and produce as varied but still readable icons when they are generated entirely on the basis of the MUD name - in a reproducible (though Qt do NOT guarantee repeatable qHash() functionality across Qt versions - so they may not stay *that* constant) manner. Refrob: use a table of fixed colours Using only 15 hues but with 3 different saturation level plus 5 grey-scale one should give enough different icons but not generate them too close to each other for those with normal colour vision... Signed-off-by: Stephen Lyons <slysven@virginmedia.com> Co-authored-by: Kebap <kebap_spam@gmx.net>
2020-12-11 17:53:28 +00:00
} else {
painter.setPen(Qt::white);
Revise: select profiles by typing the name, keep profile icons (#4440) Restore the text display of the profile name. This is so that the keyboard selection of profiles beginning with a particular letter works again. This will close issue #4283 . This also sets it to the minimum size trying to hide it by setting the font size to 1 - however that still show something (and a font size of zero is NOT valid). Manipulating the QPalette of the QListWidget does not work well enough in the presence of Styles or StyleSheets to be a viable means of hiding the text either. Also in the `dlgConnection` class: * refactor out some highly repetitive code that sets up the predefined Mud icons. * remove warnings and adopts a change introduced into Qt 5.15 for the QLabel::pixmap() which now requires a Qt::ReturnByValue argument to prevent an obsolete code warning and to force a QPixmap to be returned by value and not reference... * remove a warning about: QListWidget::setItemSelected(QListWidgetItem*,bool) being deprecated and switches to the recommended: QListWidgetItem::setSelected(bool) Edit: the last item above may have become redundant as further commits amalgamated into this PR may have taken out these lines anyhow! Change the custom icon generator so that there is NOT a dependency on the position in the list for the colours used. This means that the same name will always have the same colours - which will help the user to pick out a particular profile as it will always look the same no matter if other profiles are added or removed. Remove the colour gradient in the custom icon as it is not thought to be useful nowadays. Refactor out the colour icon generator to a single method that is called from more than one place. BugFix: restore odd original way of accessing some QPixmaps This was causing failures in some CI platforms using Qt 5.14. or older. Revise: really frob the custom icon generator This is to try and produce as varied but still readable icons when they are generated entirely on the basis of the MUD name - in a reproducible (though Qt do NOT guarantee repeatable qHash() functionality across Qt versions - so they may not stay *that* constant) manner. Refrob: use a table of fixed colours Using only 15 hues but with 3 different saturation level plus 5 grey-scale one should give enough different icons but not generate them too close to each other for those with normal colour vision... Signed-off-by: Stephen Lyons <slysven@virginmedia.com> Co-authored-by: Kebap <kebap_spam@gmx.net>
2020-12-11 17:53:28 +00:00
}
painter.setFont(font);
painter.drawText(QRect(30, 0, 90, 30), Qt::AlignCenter | Qt::TextWordWrap, text);
Revise: select profiles by typing the name, keep profile icons (#4440) Restore the text display of the profile name. This is so that the keyboard selection of profiles beginning with a particular letter works again. This will close issue #4283 . This also sets it to the minimum size trying to hide it by setting the font size to 1 - however that still show something (and a font size of zero is NOT valid). Manipulating the QPalette of the QListWidget does not work well enough in the presence of Styles or StyleSheets to be a viable means of hiding the text either. Also in the `dlgConnection` class: * refactor out some highly repetitive code that sets up the predefined Mud icons. * remove warnings and adopts a change introduced into Qt 5.15 for the QLabel::pixmap() which now requires a Qt::ReturnByValue argument to prevent an obsolete code warning and to force a QPixmap to be returned by value and not reference... * remove a warning about: QListWidget::setItemSelected(QListWidgetItem*,bool) being deprecated and switches to the recommended: QListWidgetItem::setSelected(bool) Edit: the last item above may have become redundant as further commits amalgamated into this PR may have taken out these lines anyhow! Change the custom icon generator so that there is NOT a dependency on the position in the list for the colours used. This means that the same name will always have the same colours - which will help the user to pick out a particular profile as it will always look the same no matter if other profiles are added or removed. Remove the colour gradient in the custom icon as it is not thought to be useful nowadays. Refactor out the colour icon generator to a single method that is called from more than one place. BugFix: restore odd original way of accessing some QPixmaps This was causing failures in some CI platforms using Qt 5.14. or older. Revise: really frob the custom icon generator This is to try and produce as varied but still readable icons when they are generated entirely on the basis of the MUD name - in a reproducible (though Qt do NOT guarantee repeatable qHash() functionality across Qt versions - so they may not stay *that* constant) manner. Refrob: use a table of fixed colours Using only 15 hues but with 3 different saturation level plus 5 grey-scale one should give enough different icons but not generate them too close to each other for those with normal colour vision... Signed-off-by: Stephen Lyons <slysven@virginmedia.com> Co-authored-by: Kebap <kebap_spam@gmx.net>
2020-12-11 17:53:28 +00:00
}
return QIcon(background);
}
2021-02-03 19:59:35 +00:00
void dlgConnectionProfiles::clearNotificationArea()
{
notificationArea->hide();
notificationAreaIconLabelWarning->hide();
notificationAreaIconLabelError->hide();
notificationAreaIconLabelInformation->hide();
notificationAreaMessageBox->clear();
}
void dlgConnectionProfiles::slot_reenableAllProfileItems()
{
for (int i = 0, total = listWidget_profiles->count(); i < total; ++i) {
listWidget_profiles->item(i)->setFlags(listWidget_profiles->item(i)->flags() | Qt::ItemIsEnabled);
2021-02-03 19:59:35 +00:00
}
}
bool dlgConnectionProfiles::eventFilter(QObject* obj, QEvent* event)
{
if (obj == listWidget_profiles && event->type() == QEvent::KeyPress) {
2021-02-03 19:59:35 +00:00
QKeyEvent* keyEvent = static_cast<QKeyEvent*>(event);
switch (keyEvent->key()) {
// Process all the keys that could be used in a profile name,
// i.e. the "scmAllowedProfileNameChars" list
2021-02-03 19:59:35 +00:00
default:
// For other keys handle them as normal:
return QObject::eventFilter(obj, event);
case Qt::Key_Escape:
// Clear the search:
mSearchText.clear();
slot_reenableAllProfileItems();
// Eat (filter) this event so it goes no further:
return true;
case Qt::Key_Period:
case Qt::Key_Space:
case Qt::Key_Underscore:
case Qt::Key_0:
case Qt::Key_1:
case Qt::Key_2:
case Qt::Key_3:
case Qt::Key_4:
case Qt::Key_5:
case Qt::Key_6:
case Qt::Key_7:
case Qt::Key_8:
case Qt::Key_9:
case Qt::Key_Minus:
case Qt::Key_NumberSign:
case Qt::Key_Ampersand:
case Qt::Key_ParenLeft:
case Qt::Key_ParenRight:
2021-02-03 19:59:35 +00:00
case Qt::Key_A:
case Qt::Key_B:
case Qt::Key_C:
case Qt::Key_D:
case Qt::Key_E:
case Qt::Key_F:
case Qt::Key_G:
case Qt::Key_H:
case Qt::Key_I:
case Qt::Key_J:
case Qt::Key_K:
case Qt::Key_L:
case Qt::Key_M:
case Qt::Key_N:
case Qt::Key_O:
case Qt::Key_P:
case Qt::Key_Q:
case Qt::Key_R:
case Qt::Key_S:
case Qt::Key_T:
case Qt::Key_U:
case Qt::Key_V:
case Qt::Key_W:
case Qt::Key_X:
case Qt::Key_Y:
case Qt::Key_Z:
if (keyEvent->modifiers() & ~(Qt::ShiftModifier)) {
// There is a modifier in play OTHER than the shift one so treat
// it as normal:
return QObject::eventFilter(obj, event);
}
if (!mSearchTextTimer.isActive()) {
// Too long since the last keypress so forget any previously
// entered keypresses:
mSearchText.clear();
}
mSearchTextTimer.stop();
addLetterToProfileSearch(keyEvent->key());
// Restart the timeout for another keypress:
mSearchTextTimer.start();
// Eat (filter) this event so it goes no further:
return true;
}
}
// standard event processing
return QObject::eventFilter(obj, event);
}
void dlgConnectionProfiles::addLetterToProfileSearch(const int key)
{
if ((key < 0) || (key > 128)) {
// out of range of normal ASCII keys
return;
}
// As it happens the values for key correspond to those of the corresponding
// ASCII (upper-case for letters) character codes
mSearchText.append(QLatin1Char(static_cast<unsigned char>(key)));
auto indexes = findProfilesBeginningWith(mSearchText);
if (indexes.isEmpty()) {
// No matches at all so clearing search term and reset all profiles to
// be enabled:
mSearchText.clear();
slot_reenableAllProfileItems();
return;
}
for (int i = 0, total = listWidget_profiles->count(); i < total; ++i) {
auto flags = listWidget_profiles->item(i)->flags();
2021-02-03 19:59:35 +00:00
if (indexes.isEmpty() || !indexes.contains(i)) {
flags &= ~Qt::ItemIsEnabled;
} else {
flags |= Qt::ItemIsEnabled;
}
listWidget_profiles->item(i)->setFlags(flags);
2021-02-03 19:59:35 +00:00
}
listWidget_profiles->setCurrentRow(indexes.first());
2021-02-03 19:59:35 +00:00
}
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 06:44:38 -04:00
void dlgConnectionProfiles::slot_loadPasswordAsync()
{
if (!sender()) {
return;
}
// Get the profile name from the timer's property
QTimer* timer = qobject_cast<QTimer*>(sender());
if (!timer) {
return;
}
const QString profile_name = timer->property("profileName").toString();
// Prevent duplicate password loading operations for the same profile
if (mKeychainOperationInProgress) {
return;
}
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 06:44:38 -04:00
if (profile_name.isEmpty()) {
return;
}
// Clean up the timer
timer->deleteLater();
// Check if this dialog is still valid and the profile is still selected
if (listWidget_profiles->currentItem() == nullptr) {
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 06:44:38 -04:00
return;
}
const QString currentProfileName = listWidget_profiles->currentItem()->data(csmNameRole).toString();
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 06:44:38 -04:00
if (currentProfileName != profile_name) {
// Selection has changed, ignore this async load
return;
}
// If secure storage is enabled, try keychain first, then fallback to QSettings
if (mudlet::self()->storingPasswordsSecurely()) {
mKeychainOperationInProgress = true;
auto* credManager = new CredentialManager(this);
credManager->retrievePassword(profile_name, "character", [this, credManager, profile_name](bool success, const QString& retrievedPassword, const QString& errorMessage) {
// Clear the operation flag first
mKeychainOperationInProgress = false;
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 06:44:38 -04:00
// Check if profile selection has changed while we were waiting
if (listWidget_profiles->currentItem() && listWidget_profiles->currentItem()->data(csmNameRole).toString() == profile_name) {
if (success) {
// Keychain operation succeeded - set the password (even if empty)
// Temporarily block textChanged signal to avoid triggering save on programmatic setText
{
const QSignalBlocker blocker(character_password_entry);
character_password_entry->setText(retrievedPassword);
}
if (retrievedPassword.isEmpty()) {
qDebug() << "dlgConnectionProfiles: Keychain returned empty password for" << profile_name;
} else {
qDebug() << "dlgConnectionProfiles: Successfully loaded password from keychain for" << profile_name;
}
} else {
// Fallback to QSettings only if credential retrieval failed
loadPasswordFromSettings(profile_name);
qDebug() << "dlgConnectionProfiles: Credential retrieval unsuccessful for" << profile_name << "-" << errorMessage;
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 06:44:38 -04:00
}
}
2025-09-13 14:17:13 +07:00
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 06:44:38 -04:00
// Check if there's a pending connection waiting for this password load
// (do this regardless of profile selection state to avoid hanging)
if (!mPendingProfileLoad.isEmpty() && mPendingProfileLoad == profile_name) {
qDebug() << "dlgConnectionProfiles: Password load completed, proceeding with pending connection for" << profile_name;
2025-09-13 14:17:13 +07:00
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 06:44:38 -04:00
// Clear pending state
QString profileToLoad = mPendingProfileLoad;
bool shouldConnect = mPendingConnect;
mPendingProfileLoad.clear();
2025-09-13 14:17:13 +07:00
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 06:44:38 -04:00
// Proceed with the connection
loadProfile(shouldConnect);
QDialog::accept();
}
2025-09-13 14:17:13 +07:00
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 06:44:38 -04:00
credManager->deleteLater();
});
} else {
// Secure storage disabled, use QSettings directly
loadPasswordFromSettings(profile_name);
2025-09-13 14:17:13 +07:00
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 06:44:38 -04:00
// Check if there's a pending connection waiting
if (!mPendingProfileLoad.isEmpty() && mPendingProfileLoad == profile_name) {
qDebug() << "dlgConnectionProfiles: Password loaded from settings, proceeding with pending connection for" << profile_name;
2025-09-13 14:17:13 +07:00
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 06:44:38 -04:00
// Clear pending state
QString profileToLoad = mPendingProfileLoad;
bool shouldConnect = mPendingConnect;
mPendingProfileLoad.clear();
2025-09-13 14:17:13 +07:00
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 06:44:38 -04:00
// Proceed with the connection
loadProfile(shouldConnect);
QDialog::accept();
}
}
}
void dlgConnectionProfiles::loadPasswordFromSettings(const QString& profile_name)
{
auto& settings = *mudlet::self()->mpSettings;
settings.beginGroup(qsl("profiles/%1").arg(profile_name));
// Get password and handle migration
const QString password = settings.value(qsl("password"), QString()).toString();
const QString oldPassword = settings.value(qsl("login"), QString()).toString();
// Temporarily block textChanged signal to avoid triggering save on programmatic setText
{
const QSignalBlocker blocker(character_password_entry);
if (!password.isEmpty()) {
character_password_entry->setText(password);
} else if (!oldPassword.isEmpty()) {
// Migrate old password
character_password_entry->setText(oldPassword);
settings.setValue(qsl("password"), oldPassword);
settings.remove(qsl("login"));
} else {
character_password_entry->setText(QString());
}
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 06:44:38 -04:00
}
settings.endGroup();
}
void dlgConnectionProfiles::slot_passwordTextChanged()
{
QListWidgetItem* pItem = listWidget_profiles->currentItem();
if (!pItem) {
return;
}
mPendingPasswordSaveProfile = pItem->data(csmNameRole).toString();
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 06:44:38 -04:00
// Cancel any pending password save
if (mPasswordSaveTimer) {
mPasswordSaveTimer->stop();
} else {
mPasswordSaveTimer = new QTimer(this);
mPasswordSaveTimer->setSingleShot(true);
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
mPasswordSaveTimer->setInterval(500ms); // 500ms debounce
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 06:44:38 -04:00
connect(mPasswordSaveTimer, &QTimer::timeout, this, [this]() {
if (!mPendingPasswordSaveProfile.isEmpty()) {
// Check if this profile is STILL selected - if not, don't save
// (user switched away, so the password field content is for a different profile)
QListWidgetItem* currentItem = listWidget_profiles->currentItem();
if (currentItem && currentItem->data(csmNameRole).toString() == mPendingPasswordSaveProfile) {
slot_updatePassword(character_password_entry->text());
}
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 06:44:38 -04:00
}
});
}
mPasswordSaveTimer->start();
}