Add telnet:// URI scheme support for one-click MUD connections#8601
Add telnet:// URI scheme support for one-click MUD connections#8601vadi2 merged 46 commits intoMudlet:developmentfrom
telnet:// URI scheme support for one-click MUD connections#8601Conversation
|
Hey there! Thanks for helping Mudlet improve. 🌟 Test versionsYou can directly test the changes here:
No need to install anything - just unzip and run. |
|
@coderabbitai full review |
✅ Actions performedFull review triggered. |
📝 WalkthroughWalkthroughThe changes implement telnet URI protocol handling for the Mudlet application. This includes updating the desktop file MIME type associations to include telnet scheme handling, adding macOS bundle configuration with telnet protocol support, and implementing URI parsing and profile matching logic. The system queues and forwards telnet URIs between application instances, registers the protocol handler on Windows, and supports profile auto-loading when launched with a telnet:// URI. Supporting infrastructure includes instance coordination, URI validation, profile creation, and unit tests for URI parsing functionality. ✨ Finishing touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/MudletInstanceCoordinator.cpp (1)
94-127: Potential loss of Telnet URIs when multiple arrive in quick successionThe Telnet path in
handleReadyRead()stores a singlemQueuedTelnetUriand then schedules aQTimer::singleShotthat later reads and clears it. If two or more Telnet URIs arrive before the first timer fires:
- Each
handleReadyRead()overwritesmQueuedTelnetUri.- The first timer processes the last URI.
- The subsequent timer(s) see an empty string and do nothing.
Net effect: all but the last URI are silently dropped.
If you expect only one URI at a time this may be acceptable, but if multiple telnet:// arguments or fast successive clicks are possible you likely want a queue, e.g.:
- Replace
QString mQueuedTelnetUriwithQStringList mQueuedTelnetUris.- Push each new URI and have the timer process and pop them in FIFO order.
Alternatively, use
QTimer::singleShotwith a lambda capturing a copy of the current URI rather than reading from shared state.
🧹 Nitpick comments (10)
src/MudletInstanceCoordinator.h (1)
39-43: Consider consistent synchronization for Telnet URI queue
mQueuedTelnetUriis now exposed viaqueueTelnetUri,forwardTelnetUriToRunningInstance, andreadTelnetUriQueue, but it isn’t obviously covered by the same mutex used formQueuedPackagePaths. If these APIs can ever be called off the same thread ashandleReadyRead(), you may want to guard all accesses tomQueuedTelnetUriwithmMutexfor consistency and to avoid subtle races. If everything is guaranteed to run on the GUI thread, please document that assumption.test/CMakeLists.txt (1)
122-128: Plan for re‑enabling TelnetUriTest or extracting testable unitRight now the Telnet URI tests are fully commented out, so the new parsing logic isn’t exercised in CI. The comment explains why, but it’d be good to:
- Either extract
parseTelnetUriinto a small, non‑GUI utility that this test can link against, or- Re‑enable
TelnetUriTestonce you have a sane way to link against the main library.As‑is, the test file can easily fall out of sync with the implementation without anyone noticing.
src/mudlet.h (1)
768-789: Align parseTelnetUri visibility with unit‑test strategy
TelnetUriDataand the helper methods (especiallyparseTelnetUri) are private, buttest/TelnetUriTest.cppcallsmudlet::parseTelnetUri(...)directly. As soon as you re‑enable that test, it will fail to compile.Options:
- Move
parseTelnetUriinto a small utility (e.g. a free function or helper class) that’s public/visible to tests but still internal to Mudlet, or- Keep it as a private member and make the test a
friend(less ideal, but consistent with current design), or- Switch the tests to exercise Telnet handling via the public
handleTelnetUripath instead of the parser directly.Choosing one of these now will avoid friction when you eventually wire the tests back into CMake.
test/TelnetUriTest.cpp (1)
76-97: Strengthen profile naming and collision tests
testProfileNameGenerationandtestProfileNameCollisiononly compare hard‑codedQStringvalues against themselves. They don’t touch any production code, so they can never fail unless the literals are edited.When you hook up the real profile‑generation logic (e.g.
createProfileForUrior similar), it’d be better to:
- Call that function with representative URIs, and
- Assert on the resulting profile names and suffixing behavior.
Until then, these tests are effectively placeholders.
src/MudletInstanceCoordinator.cpp (2)
102-119: Defensive check around mudlet::self() in Telnet handler lambdaIn the Telnet branch, the lambda passed to
QTimer::singleShotdoes:mudlet::self()->handleTelnetUri(mQueuedTelnetUri);without checking that
mudlet::self()is non‑null, unlikeinstallPackagesLocally(), which asserts the pointer. If this code can ever run before the mainmudletinstance has been constructed, it could crash.Consider mirroring the package code:
mudlet* app = mudlet::self(); Q_ASSERT(app); if (!app) { return; } app->handleTelnetUri(uri);This keeps behavior safe in release builds and debuggable in debug builds.
159-188: Telnet forwarding logic is straightforward; consider clearing state after sendThe
queueTelnetUri/forwardTelnetUriToRunningInstance/readTelnetUriQueuetrio is simple and readable. One small improvement: after a successful forward, you might want to clearmQueuedTelnetUriso a subsequent call doesn’t accidentally resend the same URI ifqueueTelnetUriwasn’t invoked again.if (socket.waitForConnected(WAIT_FOR_RESPONSE_MS)) { const QString message = qsl("TELNET_URI:") + mQueuedTelnetUri; socket.write(message.toUtf8()); socket.waitForBytesWritten(WAIT_FOR_RESPONSE_MS); socket.disconnectFromServer(); + mQueuedTelnetUri.clear(); return true; }Not critical, but it makes the state machine a bit tighter.
src/mudlet.cpp (2)
4365-4399: Optional: clarify whether predefined games should be matched here
findMatchingProfilecurrently:
- Builds
profileNamesfromprofiles/directory plusTGameDetails::keys().- But only matches based on
host_urlandport_numberfiles:QString profileHost = readProfileData(profileName, qsl("host_url")); QString profilePort = readProfileData(profileName, qsl("port_number")); // ... if (profileHost.toLower() == host.toLower() && profilePort.toInt() == port) { ... }For predefined games that don’t yet have an on‑disk profile (only entries in
TGameDetails), this will never match, even thoughTGameDetailsalready knows their host and port.If your intent is strictly “match existing saved profiles only”, this is fine. If instead you want “also treat predefined games as candidates even before the user has saved them”, you likely need to:
- Detect when
profileNameis only inTGameDetails(no profile dir/data).- Compare
(host,port)againstTGameDetails::findGame(profileName)->hostUrl/portas well.Otherwise, this implementation only ever considers persisted profiles, not bare predefined games.
4408-4419: Optional: profile naming withhost:portmay cause filesystem issues on WindowsFor non‑default ports, you build profile names as
hostname:port:QString profileName = uriData.host; if (uriData.port != 23) { profileName += qsl(":%1").arg(uriData.port); }These profile names are later used in filesystem paths under
profiles/. On Windows,:inside a path component is problematic (reserved / alternate data stream syntax), and even if Qt allows it, it can make manual inspection and backup tooling harder.If you want to be conservative across platforms, consider a filesystem‑safe separator, e.g.:
hostname_port,hostname (port 4000), orhostname@4000.This is cosmetic but can save portability headaches later.
src/main.cpp (2)
395-427: CLI telnet:// detection and multi-instance forwarding look sound; consider a couple of edge casesThe flow of detecting a
telnet://positional argument, queuing it viaMudletInstanceCoordinator, forwarding to an existing instance for non‑first runs, and falling back to starting a new instance if forwarding fails all looks coherent and aligns with the existing package-handling pattern.Two small follow-ups to consider:
- Ensure
MudletInstanceCoordinator::forwardTelnetUriToRunningInstance()only returnsfalsewhen the URI definitely was not processed by another instance; otherwise you could end up with both this process and the running instance using the same queued URI.- If you ever need to accept broader telnet URL variants, you might want to switch the
startsWith("telnet://", Qt::CaseInsensitive)check to something likeQUrl url(firstArg); if (url.scheme().compare("telnet", Qt::CaseInsensitive) == 0)so any validtelnet:URL form is picked up without relying on a specific prefix string.
663-671: Windows telnet:// registry registration will unconditionally take over the global handlerThis block correctly builds a quoted command string (
"<mudletExe>" "%1") and writes the expectedURL Protocolregistration keys, so the mechanics look right. However, writing directly underHKEY_CLASSES_ROOT\telnetmeans Mudlet will become the system-wide handler fortelnet://links every time it starts, overriding any existing client or prior user choice.If that’s not explicitly desired, consider a more conservative approach, e.g.:
- Only writing these keys when there is no existing
telnetregistration, or- Gating this behind a user-visible preference / first-run prompt so users can opt in to Mudlet taking over
telnet://handling.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (10)
mudlet.desktop(1 hunks)src/CMakeLists.txt(1 hunks)src/MacOSXBundleInfo.plist.in(1 hunks)src/MudletInstanceCoordinator.cpp(3 hunks)src/MudletInstanceCoordinator.h(2 hunks)src/main.cpp(3 hunks)src/mudlet.cpp(1 hunks)src/mudlet.h(2 hunks)test/CMakeLists.txt(1 hunks)test/TelnetUriTest.cpp(1 hunks)
🧰 Additional context used
🧬 Code graph analysis (3)
src/MudletInstanceCoordinator.cpp (1)
src/mudlet.cpp (2)
self(138-141)self(138-138)
src/MudletInstanceCoordinator.h (1)
src/MudletInstanceCoordinator.cpp (6)
queueTelnetUri(159-162)queueTelnetUri(159-159)forwardTelnetUriToRunningInstance(166-184)forwardTelnetUriToRunningInstance(166-166)readTelnetUriQueue(186-189)readTelnetUriQueue(186-186)
src/main.cpp (1)
src/mudlet.cpp (2)
self(138-141)self(138-138)
🔇 Additional comments (6)
src/CMakeLists.txt (1)
719-725: MACOSX_BUNDLE_INFO_PLIST wiring looks correctUsing
MACOSX_BUNDLE_INFO_PLISTto point atMacOSXBundleInfo.plist.inis the right way to hook the new Info.plist template into the macOS bundle; no issues from the CMake side.src/MacOSXBundleInfo.plist.in (1)
1-42: Info.plist template correctly declares Telnet URL handlerThe plist template looks well‑formed, and the
CFBundleURLTypesblock for thetelnetscheme (roleViewer) should be enough for macOS to register Mudlet as a Telnet URL handler. The CMake placeholders line up with the properties set insrc/CMakeLists.txt.src/mudlet.h (1)
352-354: Public Telnet URI entry point is a good abstractionExposing a single
handleTelnetUri(const QString& uri)onmudletis a clean way to keep Telnet handling behind the main window API and aligns with the instance coordinator calling pattern.test/TelnetUriTest.cpp (1)
25-47: parseTelnetUri tests depend on private APIThese tests call
mudletInstance.parseTelnetUri(...), butparseTelnetUriis currently private inmudlet. That's consistent with why TelnetUriTest is commented out in CMake; as soon as you re‑enable it, compilation will fail.Once you decide how to expose or refactor the parsing logic (utility function, friend, or public API), please update these tests accordingly so they compile and exercise the real implementation.
src/mudlet.cpp (1)
4327-4350: Strengthen telnet URI validation (URL validity and port range)The review's technical concerns about
parseTelnetUriare sound. Web search confirms:
QUrl::port(23)returns 23 both when no port is explicitly specified AND when a port is present—making the distinction unclear.QUrl::isValid()checks only generic RFC syntax, not telnet-specific semantics; however, it will catch some malformed URIs.- The suggested approach using
url.port(-1)to explicitly distinguish "no port" from "port present" is the correct pattern.- Valid port range in Qt is 0–65535.
The suggested refinement is valid:
- Add
url.isValid()for syntax validation- Use
url.port(-1)to distinguish absent ports from present ones- Explicitly reject ports outside 1–65535
This approach makes failures explicit rather than silently defaulting to port 23.
Unable to verify the actual code implementation in
src/mudlet.cppdue to repository access limitations; however, the technical foundation of the review is correct based on Qt's documented behavior.src/main.cpp (1)
713-721: Deferred startup handling cleanly prioritises telnet URIs over normal auto-loginDeferring startup behaviour with
QTimer::singleShot(0, qApp, ...)and branching ontelnetUri.isEmpty()nicely ensuresmudlet::self()is initialised before you either callhandleTelnetUri()orstartAutoLogin(cliProfiles). CapturingcliProfilesandtelnetUriby value keeps the lambda safe and self-contained. This integration looks good.
|
Using an actual webpage on the web (https://store.chipkin.com/articles/telnet-list-of-telnet-servers for example), the link doesn't seem to work: Screencast.from.2025-12-05.14-55-20.mp4 |
@vadi2 What OS ? |
|
That's on Windows |
|
@vadi2 It seems to work for me perfectly even with your links. Maybe you may have permissions blocking this. Did you run Mudlet normally first ? SEE VIDEO OF IT WORKING: 2025-12-05.15-02-17.mp4 |
|
As you can see in the video, I run Mudlet first in 0:05 |
|
@vadi2 what do you suspect may be happening ? |
|
maybe you should restart your machine or change some setings or maybe some antivirus software or that it just dosen't work first try |
|
The idea behind the feature is that it just works for the players, without changing AV settings. |
|
/refresh links |
|
@vadi2 @ZookaOnGit I've verified that clicking on telnet links now works on Linux 2025-12-20.15-11-09.mp4 |
|
Testing the PR build in a Windows OS environment is producing TWO problems - one being due to the operating conditions and one which seems to be a defect - possibly introduced by other changes.
#elif defined(Q_OS_WINDOWS)
// Running from an extracted .zip archive requires the C search path
// to also be set to the directory of the executable:
additionalCPaths << qsl("%1/?.dll").arg(appPath);this is what I deduced from the following:
- although I note that a new icon for this new profile was correctly added after closing and reopening the dialogue - which IS correct.
I think @Excellencedev will need to address 2. |
Co-authored-by: Stephen Lyons <slysven@virginmedia.com>
|
Humm, after trying another PR's Windows 🤔 |
|
@SlySven Your 1st point has been addressed in 95ff228 https://github.com/user-attachments/assets/5076d77c-61d6-4d7e-aeb7-138dcb1921e3. OBS ruined my video |
|
/refresh links |
After telling my web-browser (PaleMoon) that I wanted to use the
Putting the link telnet://ancientempires.net:5011 into my text editor ( 👍 As far as I can tell this PR is functionally complete for the Windows OS. The only "missing-link" might be making the Mudlet application being the application to open such |
|
Alright, thanks for testing |
|
Fixed conflicts |
|
Code review notes:
src/main.cpp:978 and src/main.cpp:1038-1042 The comment at line 974 explains why migration was moved from a 2-second timer to an immediate call ("which created a race: the connection dialog could open and attempt to load passwords before migration had a chance to run"). But the PR re-adds the 2-second timer version at line 1038. This is redundant and reintroduces the race the immediate call was designed to fix.
src/mudlet.h:203,402 and src/mudlet.cpp:6941-6944 The method and member are declared and implemented but never called. FileOpenHandler is a stack variable in main() (line 933). Either use the ownership transfer pattern or remove the dead code.
src/MudletInstanceCoordinator.h:44 and src/MudletInstanceCoordinator.cpp:197-201 Declared, implemented, never called anywhere.
src/MudletInstanceCoordinator.cpp:47-50 Neither socket.write() nor waitForBytesWritten() return values are checked, but the function returns true regardless. The caller in main.cpp:486 uses this to decide whether to exit the second instance (return 0). If the write silently fails, the second instance exits and the packages are never installed. Contrast with forwardTelnetUriToRunningInstance() which correctly checks both.
src/main.cpp:796-801 If xdg-mime isn't installed (minimal systems, containers, some Wayland setups), start() fails, readAllStandardOutput() returns empty, existingHandlerFound becomes false, and Mudlet silently registers itself without asking the user. "Tool not found" is conflated with "no handler registered." Should check xdgQuery.error() after start() to distinguish the two cases.
src/main.cpp:748-765 If CFURLGetFileSystemRepresentation fails, the code falls through with forceAsk still false. We know a handler exists (got a valid appUrl) but can't resolve its path. Mudlet then assumes it IS the registered handler and doesn't ask. Should set forceAsk = true in the else branch.
src/main.cpp:828 "You can change this later in Settings > General." - no such toggle exists in the preferences dialog. Please implement the toggle so users can change their choice.
src/mudlet.cpp:6944-6945 - no blank line between takeOwnershipOfFileOpenHandler() and setGlobalStyleSheet().
src/MudletInstanceCoordinator.cpp:117-120 The outer loop already splits on '\n' at line 104. QChar::LineFeed is '\n'. The inner split will always produce a single-element list. Should just do mQueuedPackagePaths << message. |
|
@vadi2 Thank you for reviewing and looking forward to future contributions to this project |












Brief overview of PR changes/additions
Summary
Implements full support for
telnet://links in Mudlet, allowing users to connect to MUD servers with a single click from browsers or other applications.Usage Example
Users can now click links like:
Or run from command line:
mudlet "telnet://testserver.com:2000"Mudlet will:
TESTING
I ran it on WEB and it worked fine. I ran it from command line, it worked fine
HERE IS A DEMO VIDEO : https://github.com/user-attachments/assets/143cc98a-3453-4bd9-bbe8-5b48c58db490
Motivation for adding to Mudlet
The bounty and to improve myself and contribute
Other info (issues closed, discussion etc)
/closes #689
/claim #689
CHECKLIST:
[x] Windows
[x] Linux
[x] macOS