mudlet/src/Host.h
Mike Conley c4849b6651
improve: OSC 8 hyperlink handling, and a setting to turn it off (#9731)
#### Brief overview of PR changes/additions
- Hardens how OSC 8 link payloads and link text are handled before they
are run or displayed; link commands are no longer built by
string-formatting remote text into Lua source.
- Adds a per-profile setting (General → Game protocols) to turn OSC 8
hyperlinks off, which also reports `0` for every `OSC_HYPERLINKS*`
NEW-ENVIRON variable and sends an INFO update if toggled mid-session.
- Fixes `selected=` callbacks on `send:` links, which never fired, and
keeps emoji and Persian/Arabic/Indic text intact in tooltips and menu
labels.

#### Motivation for adding to Mudlet

Inspired by
[conversation](https://discord.com/channels/279748146316312576/1416447642472284261/1535109010066251890)
on the MUD Discord and updates to terminal emulators.

OSC 8 sequences arrive from the game server — and often from another
player whose say/tell text the server relays — so they have to be
treated as untrusted input rather than as content the user chose to
load.

#### Other info (issues closed, discussion etc)
New unit tests: `LuaLiteralTest` (28 cases, including an exhaustive
sweep over the bracket alphabet, each evaluated in a real Lua 5.1 state)
and `UntrustedTextTest` (26 cases covering emoji sequences, non-Latin
shaping and the two sanitization policies). There is no automated
NEW-ENVIRON coverage anywhere in the repo, so that path was verified
manually against a live server instead.

**Test case:**
1. `say !osc8-docs` — every documented feature still works.
2. Send a link whose command ends in `]`, e.g. `send:say [OOC]` —
clicking sends the literal text (previously the click silently did
nothing).
3. Settings → General → Game protocols → uncheck "Enable OSC 8
hyperlinks from the server" — links stop rendering and the server is
told without a reconnect; re-check and they return.
4. Send a tooltip or menu label containing a multi-part emoji such as
👨‍🍳 — it renders normally, not as its component parts.

---------

Signed-off-by: Michael Conley <sousesider@gmail.com>
2026-08-08 10:09:32 +02:00

1166 lines
52 KiB
C++

#ifndef MUDLET_HOST_H
#define MUDLET_HOST_H
/***************************************************************************
* Copyright (C) 2008-2013 by Heiko Koehn - KoehnHeiko@googlemail.com *
* Copyright (C) 2014 by Ahmed Charles - acharles@outlook.com *
* Copyright (C) 2015-2020, 2022-2023, 2025 by Stephen Lyons *
* - slysven@virginmedia.com *
* Copyright (C) 2016 by Ian Adkins - ieadkins@gmail.com *
* Copyright (C) 2018 by Huadong Qi - novload@outlook.com *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program; if not, write to the *
* Free Software Foundation, Inc., *
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
***************************************************************************/
#include "ActionUnit.h"
#include "AliasUnit.h"
#include "KeyUnit.h"
#include "ScriptUnit.h"
#include "GifTracker.h"
#include "TCommandLine.h"
#include "TLuaInterpreter.h"
#include "TimerUnit.h"
#include "TMainConsole.h"
#include "TriggerUnit.h"
#include "ctelnet.h"
#include "dlgTriggerEditor.h"
#include "enums.h"
#include <QColor>
#include <QFile>
#include <QFont>
#include <QFuture>
#include <QList>
#include <QMargins>
#include <QPointer>
#include <QRect>
#include <QStack>
#include <QTextStream>
#include <memory>
#include "TMxpMudlet.h"
#include "TMxpProcessor.h"
#include "TMxpFrameManager.h"
namespace pugi {
class xml_document;
}
class QDockWidget;
class QJsonObject;
class QKeyEvent;
class TEvent;
class TArea;
class LuaInterface;
class XMLexport;
class TMedia;
class GMCPAuthenticator;
class TRoom;
class TConsole;
class TMainConsole;
class dlgNotepad;
class TMap;
class MMCPServer;
class dlgIRC;
class dlgPackageManager;
class dlgModuleManager;
class dlgProfilePreferences;
class cTelnet;
class stopWatch
{
friend class XMLimport;
public:
stopWatch();
bool start();
bool stop();
bool reset();
bool running() const { return mIsRunning; }
void adjustMilliSeconds(const qint64);
qint64 getElapsedMilliSeconds() const;
QString getElapsedDayTimeString() const;
void setPersistent(const bool state) { mIsPersistent = state; }
bool persistent() const { return mIsPersistent; }
void setName(const QString& name) { mName = name; }
const QString& name() const { return mName; }
#ifndef QT_NO_DEBUG_STREAM
// Only used for debugging:
bool initialised() const { return mIsInitialised; }
qint64 elapsed() const { return mElapsedTime; }
QDateTime effectiveStartDateTime() const { return mEffectiveStartDateTime; }
#endif // QT_NO_DEBUG_STREAM
private:
// true once started the first time - but provides some code short cuts if
// prior to that:
bool mIsInitialised;
// true when RUNNING, false when STOPPED:
bool mIsRunning;
// If true this stopwatch is saved with the profile and reloaded - if it is
// running when saved it will seem to continue to run - so can be used to
// track real time events outside of the profile, it is intended that the
// parent Host class that makes use of it will save and restore the id
// number that that class assigns the stopwatch:
bool mIsPersistent;
// When RUNNING this contains the effective point in time when the stop
// watch was started - so that taking a difference between then and
// "now" gives the total elapsed time:
QDateTime mEffectiveStartDateTime;
// When STOPPED this contains the cumulative elasped time in milliSeconds:
qint64 mElapsedTime;
// As the id is generated according to what other ones have been created but
// persists between saves it is useful for the user or script writer to be
// able to name a particular stop watch for identification later:
QString mName;
};
#ifndef QT_NO_DEBUG_STREAM
inline QDebug& operator<<(QDebug& debug, const stopWatch& stopwatch)
{
const QDebugStateSaver saver(debug);
Q_UNUSED(saver)
debug.nospace() << qsl("stopwatch(mIsRunning: %1 mInitialised: %2 mIsPersistent: %3 mEffectiveStartDateTime: %4 mElapsedTime: %5)")
.arg((stopwatch.running() ? QLatin1String("true") : QLatin1String("false")),
(stopwatch.initialised() ? QLatin1String("true") : QLatin1String("false")),
(stopwatch.persistent() ? QLatin1String("true") : QLatin1String("false")),
stopwatch.effectiveStartDateTime().toString(),
QString::number(stopwatch.elapsed()));
return debug;
}
#endif // QT_NO_DEBUG_STREAM
class Host : public QObject
{
Q_OBJECT
friend class XMLexport;
friend class XMLimport;
friend class dlgProfilePreferences;
// Allows the functional test to set the Discord username restriction:
friend class TDiscordModeTest;
public:
Host(int port, const QString& mHostName, const QString& login, const QString& pass, int host_id);
~Host();
enum DiscordOptionFlag {
DiscordNoOption = 0x0,
DiscordSetDetail = 0x01,
DiscordSetState = 0x02,
DiscordSetLargeIcon = 0x04,
DiscordSetLargeIconText = 0x08,
DiscordSetSmallIcon = 0x10,
DiscordSetSmallIconText = 0x20,
DiscordSetPartyInfo = 0x80,
DiscordSetTimeInfo = 0x100,
DiscordSetSubMask = 0x3ff,
// Kept for backward compatibility with saved profiles but no longer checked:
DiscordLuaAccessEnabled = 0x800
};
Q_DECLARE_FLAGS(DiscordOptionFlags, DiscordOptionFlag)
enum DiscordMode { DiscordDisabled = 0, DiscordShowMudletOnly = 1, DiscordShowGameDetails = 2 };
QString getName() { return mHostName; }
QString getCommandSeparator() { return mCommandSeparator; }
void setName(const QString& name);
QString getUrl() { return mUrl; }
void setUrl(const QString& url) { mUrl = url; }
QString getDiscordGameName() { return mDiscordGameName; }
void setDiscordGameName(const QString& name) { mDiscordGameName = name; }
int getPort() { return mPort; }
void setPort(const int port) { mPort = port; }
void setAutoReconnect(const bool reconnect) { mTelnet.setAutoReconnect(reconnect); }
QString& getLogin() { return mLogin; }
void setLogin(const QString& login) { mLogin = login; }
QString& getPass() { return mPass; }
void setPass(const QString& password) { mPass = password; }
bool hasAutoLoginCredentials() const { return !mLogin.isEmpty() && !mPass.isEmpty(); }
// True once the user has sent any command to the game on the current connection. It gates whether
// an unsolicited GMCP sign-in address may auto-open the browser: one that arrives only after the
// player acted (e.g. chose a provider on the game's own sign-in screen) is a consequence of their
// input, whereas one at an untouched connection is not and must not silently launch a browser.
// GMCPAuthenticator clears it again when it auto-opens, so one player action can launch at most
// one browser hand-off however many sign-in addresses the game pushes.
bool userSentInputThisConnection() const { return mUserSentInputThisConnection; }
void setUserSentInputThisConnection(const bool b) { mUserSentInputThisConnection = b; }
int getRetries() { return mRetries; }
void setRetries(const int retries) { mRetries = retries; }
int getTimeout() { return mTimeout; }
void setTimeout(const int seconds) { mTimeout = seconds; }
bool wideAmbiguousEAsianGlyphs() { return mWideAmbigousWidthGlyphs; }
// Uses PartiallyChecked to set the automatic mode, otherwise Checked/Unchecked means use wide/narrow ambiguous glyphs
void setWideAmbiguousEAsianGlyphs(Qt::CheckState state);
// Is used to set preference dialog control directly:
Qt::CheckState getWideAmbiguousEAsianGlyphsControlState() { return mAutoAmbigousWidthGlyphsSetting ? Qt::PartiallyChecked : (mWideAmbigousWidthGlyphs ? Qt::Checked : Qt::Unchecked); }
bool getEnableBlinkText() const { return mEnableBlinkText; }
void setEnableBlinkText(bool enable);
void setHaveColorSpaceId(const bool state) { mSGRCodeHasColSpaceId = state; }
bool getHaveColorSpaceId() { return mSGRCodeHasColSpaceId; }
void setMayRedefineColors(const bool state) { mServerMayRedefineColors = state; }
bool getMayRedefineColors() { return mServerMayRedefineColors; }
bool shouldStripOscHyperlinkConfigParam();
bool shouldStripOscHyperlinkPresetParam();
void setDiscordApplicationID(const QString& s);
const QString& getDiscordApplicationID();
void setDiscordInviteURL(const QString& s);
const QString& getDiscordInviteURL() const { return mDiscordInviteURL; }
void setSpellDic(const QString&);
QString getSpellDic();
void setUserDictionaryOptions(const bool useDictionary, const bool useShared);
void getUserDictionaryOptions(bool& useDictionary, bool& useShared)
{
useDictionary = mEnableUserDictionary;
useShared = mUseSharedDictionary;
}
void setControlCharacterMode(const ControlCharacterMode mode);
ControlCharacterMode getControlCharacterMode() const { return mControlCharacter; }
bool getLargeAreaExitArrows() const { return mLargeAreaExitArrows; }
void setLargeAreaExitArrows(const bool);
bool getUseModern3DMapper() const { return experimentEnabled(qsl("experiment.3dmap.modernmapper")); }
// Experiment system methods
bool experimentEnabled(const QString& experimentKey) const;
std::pair<bool, QString> setExperimentEnabled(const QString& experimentKey, bool enabled);
QString getActiveExperimentInGroup(const QString& group) const;
QStringList getAllExperiments() const;
QStringList getValidExperiments() const;
void forceClose();
bool profileResetInProgress() const { return mResetProfile; }
bool isClosingDown() const { return mIsClosingDown; }
bool isClosingForced() const { return mForcedClose; }
bool requestClose();
unsigned int assemblePath();
bool checkForMappingScript();
bool checkForCustomSpeedwalk();
TriggerUnit* getTriggerUnit() { return &mTriggerUnit; }
TimerUnit* getTimerUnit() { return &mTimerUnit; }
AliasUnit* getAliasUnit() { return &mAliasUnit; }
ActionUnit* getActionUnit() { return &mActionUnit; }
KeyUnit* getKeyUnit() { return &mKeyUnit; }
ScriptUnit* getScriptUnit() { return &mScriptUnit; }
GifTracker* getGifTracker() { return &mGifTracker; }
void send(QString cmd, bool wantPrint = true, bool dontExpandAliases = false);
int getHostID() { return mHostID; }
void setHostID(int id) { mHostID = id; }
TLuaInterpreter* getLuaInterpreter() { return &mLuaInterpreter; }
LuaInterface* getLuaInterface() { return mLuaInterface.data(); }
void incomingStreamProcessor(const QString& paragraph, int line);
void postIrcMessage(const QString&, const QString&, const QString&);
void enableTimer(const QString&);
void disableTimer(const QString&);
void enableTrigger(const QString&);
void disableTrigger(const QString&);
void enableKey(const QString&);
void disableKey(const QString&);
bool killTimer(const QString&);
bool killTrigger(const QString&);
QPair<int, QString> createStopWatch(const QString&);
bool destroyStopWatch(const int);
bool adjustStopWatch(const int, const qint64 milliSeconds);
QList<int> getStopWatchIds() const;
QPair<bool, double> getStopWatchTime(const int) const;
QPair<bool, QString> getBrokenDownStopWatchTime(const int) const;
bool makeStopWatchPersistent(const int, const bool);
QPair<bool, QString> resetStopWatch(const int);
QPair<bool, QString> resetStopWatch(const QString&);
QPair<bool, QString> startStopWatch(const int);
QPair<bool, QString> startStopWatch(const QString&);
QPair<bool, QString> stopStopWatch(const int);
QPair<bool, QString> stopStopWatch(const QString&);
stopWatch* getStopWatch(const int id) const
{
auto it = mStopWatchMap.find(id);
return (it != mStopWatchMap.end()) ? it->second.get() : nullptr;
}
int findStopWatchId(const QString&) const;
QPair<bool, QString> setStopWatchName(const int, const QString&);
QPair<bool, QString> setStopWatchName(const QString&, const QString&);
QPair<bool, QString> resetAndRestartStopWatch(const int);
void startSpeedWalk();
void startSpeedWalk(int sourceRoom, int targetRoom);
void reloadModule(const QString& reloadModuleName, const QString& syncingFromHost = QString());
std::pair<bool, QString> changeModuleSync(const QString& enableModuleName, const QLatin1String& value);
std::pair<bool, QString> getModuleSync(const QString& moduleName);
bool blockScripts() { return mBlockScriptCompile; }
void refreshPackageFonts();
void registerEventHandler(const QString&, TScript*);
void registerAnonymousEventHandler(const QString& name, const QString& fun);
void unregisterEventHandler(const QString&, TScript*);
void raiseEvent(const TEvent& event);
// This disables all the triggers/timers/keys in preparation to resetting
// them - and sets a timer to do resetProfile_phase2() when it is safe to do
// so. We need to do it this way because a lua script containing the call to
// produce this action will be purged from the Lua system as part of the
// reset - which causes nasty existential issues (and crashes) from deleting
// a script as it is being interpreted!
bool resetProfile_phase1();
// This actually does the bulk of the reset but must wait until the profile
// is quiescent:
void resetProfile_phase2();
std::tuple<bool, QString, QString> saveProfile(const QString& saveLocation = QString(), const QString& saveName = QString(), bool syncModules = false);
std::tuple<bool, QString, QString> saveProfileAs(const QString& fileName);
void stopAllTriggers();
void reenableAllTriggers();
// get Search Engine
QPair<QString, QString> getSearchEngine();
void set_USE_IRE_DRIVER_BUGFIX(bool b)
{
mUSE_IRE_DRIVER_BUGFIX = b;
mTelnet.set_USE_IRE_DRIVER_BUGFIX(b);
}
void updateDisplayDimensions();
std::pair<bool, QString> installPackage(const QString& fileName, enums::PackageModuleType thing, bool quiet = false);
bool uninstallPackage(const QString&, enums::PackageModuleType thing);
bool removeDir(const QString&, const QString&);
void readPackageConfig(const QString&, QString&, bool);
QString getPackageConfig(const QString&, bool isModule = false);
void postMessage(const QString message) { mTelnet.postMessage(message); }
QColor getAnsiColor(const int ansiCode, const bool isBackground = false) const;
QPair<bool, QString> writeProfileData(const QString&, const QString&);
bool writeProfileIniData(const QString& item, const QString& what);
QString readProfileData(const QString&);
QString readProfileIniData(const QString& item);
void xmlSaved(const QString& xmlName);
bool currentlySavingProfile();
// Whether a package install or uninstall still owes the profile a save - see
// mDeferredSaveTimer.
bool hasPendingProfileSave() const { return mDeferredSaveTimer.isActive(); }
void processDiscordGMCP(const QString& packageMessage, const QString& data);
void waitForProfileSave();
void clearDiscordData();
void processDiscordMSDP(const QString& variable, QString value);
void setDiscordMode(DiscordMode mode);
bool discordUserIdMatch(const QString& userName) const;
const QString& getMMCPChatName() const;
quint16 getMMCPPort();
const QString& getMMCPChatPrefix() const;
bool getMMCPAutoStartServer();
bool getMMCPAllowPeekRequests();
bool getMMCPPrefixEmotes();
bool getMMCPAddChatMessageNewline();
bool getMMCPAutoAcceptCalls();
bool getMMCPShowSnoopInMainConsole();
void setMmpMapLocation(const QString& data);
QString getMmpMapLocation() const;
void setMediaLocationGMCP(const QString& mediaUrl);
QString mediaLocationGMCP() const;
void setMediaLocationMSP(const QString& mediaUrl);
QString mediaLocationMSP() const;
// Use this rather than accessing the TMainConsole::font() as the latter
// isn't always around during profile start-up:
QFont getDisplayFont();
QFont getAndClearTempDisplayFont();
std::pair<bool, QString> setDisplayFont(const QFont&);
void setDisplayFontFromString(const QString&);
void setDisplayFontSize(int size);
QFont createFontWithSettings(const QString& fontName, int pointSize) const;
std::pair<QString, QFont::Weight> parseFontNameAndStyle(const QString& fontName) const;
int getConsoleBufferSize() const { return mConsoleBufferSize; }
void setConsoleBufferSize(int size) { mConsoleBufferSize = size; }
bool getUseMaxConsoleBufferSize() const { return mUseMaxConsoleBufferSize; }
void setUseMaxConsoleBufferSize(bool useMax) { mUseMaxConsoleBufferSize = useMax; }
void updateProxySettings(QNetworkAccessManager* manager);
std::unique_ptr<QNetworkProxy>& getConnectionProxy();
void updateAnsi16ColorsInTable();
void updateExtendedAnsiColorsInTable();
// Store/retrieve all the settings in one call:
void setPlayerRoomStyleDetails(const quint8 styleCode, const quint8 outerDiameter = 120, const quint8 innerDiameter = 70, const QColor& outerColor = QColor(), const QColor& innerColor = QColor());
void getPlayerRoomStyleDetails(quint8& styleCode, quint8& outerDiameter, quint8& innerDiameter, QColor& outerColor, QColor& innerColor);
void setSearchOptions(const dlgTriggerEditor::SearchOptions);
void setBufferSearchOptions(const TConsole::SearchOptions);
std::pair<bool, QString> setMapperTitle(const QString&);
std::optional<QString> getMapperTitle() const;
QDockWidget* mapWidget() const;
// Multiple map views support
std::pair<int, QString> createMapView(int areaId = 0);
std::pair<bool, QString> closeMapView(int viewId);
std::pair<int, QString> closeAllMapViews();
QList<int> getMapViewIds() const;
void setDebugShowAllProblemCodepoints(const bool);
bool debugShowAllProblemCodepoints() const { return mDebugShowAllProblemCodepoints; }
void setCompactInputLine(const bool state);
bool getCompactInputLine() const { return mCompactInputLine; }
QPointer<TConsole> findConsole(QString name);
QPair<bool, QStringList> getLines(const QString& windowName, const int lineFrom, const int lineTo);
std::pair<bool, QString> openWindow(const QString& name, bool loadLayout, bool autoDock, const QString& area);
std::pair<bool, QString> createMiniConsole(const QString& windowname, const QString& name, int x, int y, int width, int height);
std::pair<bool, QString> createScrollBox(const QString& windowname, const QString& name, int x, int y, int width, int height) const;
std::pair<bool, QString> createLabel(const QString& windowname, const QString& name, int x, int y, int width, int height, bool fillBg, bool clickthrough);
bool setClickthrough(const QString& name, bool clickthrough);
bool setLabelStyleSheet(const QString& name, const QString& styleSheet);
bool setLinkStyle(const QString& name, const QString& linkColor, const QString& linkVisitedColor, bool underline);
bool resetLinkStyle(const QString& name);
bool clearVisitedLinks(const QString& name);
void hideMudletsVariables();
bool createBuffer(const QString& name);
QSize calcFontSize(const QString& windowName);
bool clearWindow(const QString&);
bool showWindow(const QString&);
bool hideWindow(const QString&);
bool resizeWindow(const QString&, int, int);
bool moveWindow(const QString& name, int, int);
std::pair<bool, QString> setWindow(const QString& windowname, const QString& name, int x1, int y1, bool show);
std::pair<bool, QString> openMapWidget(const QString& area, int x, int y, int width, int height);
std::pair<bool, QString> closeMapWidget();
std::optional<QRect> mapWidgetGeometry() const;
bool closeWindow(const QString&);
bool echoWindow(const QString&, const QString&);
bool pasteWindow(const QString& name);
void loadPackageInfo();
bool setCmdLineAction(const QString&, const int);
bool resetCmdLineAction(const QString&);
bool setLabelClickCallback(const QString&, const int);
bool setLabelDoubleClickCallback(const QString&, const int);
bool setLabelReleaseCallback(const QString&, const int);
bool setLabelMoveCallback(const QString&, const int);
bool setLabelWheelCallback(const QString&, const int);
bool setLabelOnEnter(const QString&, const int);
bool setLabelOnLeave(const QString&, const int);
std::pair<bool, QString> setMovie(const QString& labelName, const QString& moviePath);
bool setBackgroundColor(const QString& name, int r, int g, int b, int alpha);
bool setCommandBackgroundColor(const QString& name, int r, int g, int b, int alpha);
bool setCommandForegroundColor(const QString& name, int r, int g, int b, int alpha);
std::optional<QColor> getBackgroundColor(const QString& name) const;
bool setBackgroundImage(const QString& name, QString& path, int mode, bool fullWindow = false);
bool resetBackgroundImage(const QString& name, bool fullWindow = false);
void showHideOrCreateMapper(const bool loadDefaultMap);
bool setProfileStyleSheet(const QString& styleSheet);
void check_for_mappingscript();
void setupIreDriverBugfix();
void setDockLayoutUpdated(const QString&);
void setToolbarLayoutUpdated(TToolBar*);
bool commitLayoutUpdates(bool flush = false);
void setScreenDimensions(const int width, const int height)
{
mScreenWidth = width;
mScreenHeight = height;
}
std::optional<QString> windowType(const QString& name) const;
std::optional<QRect> windowGeometry(const QString& name) const;
std::optional<bool> windowVisible(const QString& name) const;
bool getEditorShowBidi() const { return mEditorShowBidi; }
void setEditorShowBidi(const bool);
bool caretEnabled() const;
void setCaretEnabled(bool enabled);
bool caretShortcutMatches(const QKeyEvent*) const;
void setFocusOnHostActiveCommandLine();
void recordActiveCommandLine(TCommandLine*);
void forgetCommandLine(TCommandLine*);
QPointer<TConsole> parentTConsole(QObject*) const;
QMargins borders() const { return mBorders; }
QMargins userBorders() const { return mUserBorders; }
void setUserBorders(const QMargins);
void setMxpBorders(const QMargins);
void loadMap();
std::tuple<QString, bool> getCmdLineSettings(const TCommandLine::CommandLineType, const QString&);
void setCmdLineSettings(const TCommandLine::CommandLineType, const bool, const QString&);
int getCommandLineHistorySaveSize() const { return mCommandLineHistorySaveSize; }
void setCommandLineHistorySaveSize(const int lines);
bool showIdsInEditor() const { return mShowIDsInEditor; }
void initMMCPServer();
bool setMMCPChatName(const QString&);
void setShowIdsInEditor(const bool isShown)
{
mShowIDsInEditor = isShown;
if (mpEditorDialog) {
mpEditorDialog->showIDLabels(isShown);
}
}
bool getF3SearchEnabled() const { return mF3SearchEnabled; }
void setF3SearchEnabled(const bool enabled)
{
mF3SearchEnabled = enabled;
if (mpConsole) {
mpConsole->setF3SearchEnabled(enabled);
}
}
bool getForceMXPProcessorOn() const { return mForceMXPProcessorOn; }
void setForceMXPProcessorOn(bool value)
{
if (mForceMXPProcessorOn != value) {
mForceMXPProcessorOn = value;
emit signal_forceMXPProcessorOnChanged(value);
}
}
void sendCmdLine(const QString& cmd);
bool fontsAntiAlias() const { return !mNoAntiAlias; }
private:
bool mNoAntiAlias = false;
// These are used only during profile initiation to provide faked details
// for things looking to the main console font before it gets instantiated:
std::optional<TFontAttributes> mTempDisplayFontAttributes;
std::optional<QFont> mTempDisplayFont;
public:
// Make this the first public member instantiated so we can use ITS font
// as the "reference" or "master" font for whole profile - and so we don't
// have to maintain a separate one here in this class which does not, as
// something derived from a QObject, have one:
QPointer<TMainConsole> mpConsole;
cTelnet mTelnet;
QPointer<dlgPackageManager> mpPackageManager;
QPointer<dlgModuleManager> mpModuleManager;
TLuaInterpreter mLuaInterpreter;
bool mDisablePasswordMasking = false;
int commandLineMinimumHeight = 30;
bool mAlertOnNewData = true;
bool mAllowToSendCommand = true;
bool mAutoClearCommandLineAfterSend = false;
bool mHighlightHistory = true;
// Set in constructor and used in (bool) TScript::setScript(const QString&)
// to prevent compilation of the script that was being set therein, cleared
// after the main TConsole for a new profile has been created during the
// period when mIsProfileLoadingSequence has been set:
bool mBlockScriptCompile = true;
bool mBlockStopWatchCreation = true;
bool mEchoLuaErrors = false;
QString mCommandSeparator = qsl(";;");
bool mEnableGMCP = true;
bool mEnableMSSP = true;
bool mEnableMSDP = false;
bool mEnableMSP = true;
bool mEnableMTTS = true;
bool mEnableMNES = false;
bool mEnableMXP = true;
bool mEnableCHARSET = true;
bool mEnableNAWS = true;
bool mEnableNEWENVIRON = true;
bool mPromptedForMXPProcessorOn = false;
bool mAskTlsAvailable = true;
bool mPromptedForVersionInTTYPE = false;
int mMSSPTlsPort = 0;
QString mMSSPHostName;
TMxpMudlet mMxpClient;
TMxpProcessor mMxpProcessor;
TMxpFrameManager mMxpFrameManager;
QString mMediaLocationGMCP;
QString mMediaLocationMSP;
QTextStream mErrorLogStream;
QMap<QString, QList<TScript*>> mEventHandlerMap;
bool mFORCE_GA_OFF = false;
bool mFORCE_NO_COMPRESSION = false;
bool mFORCE_SAVE_ON_EXIT = true;
bool mSslTsl = false;
bool mSslIgnoreExpired = false;
bool mSslIgnoreSelfSigned = false;
bool mSslIgnoreAll = false;
bool mUseProxy = false;
QString mProxyAddress;
quint16 mProxyPort = 0;
QString mProxyUsername;
QString mProxyPassword;
// Used to force the test compilation of the scripts for TActions ("Buttons")
// that are pushdown buttons that run when they are "pushed down" during
// loading even though the buttons start out with themselves NOT being
// pushed down:
bool mIsProfileLoadingSequence = false;
QPointer<dlgTriggerEditor> mpEditorDialog;
QScopedPointer<TMap> mpMap;
QScopedPointer<TMedia> mpMedia;
QScopedPointer<GMCPAuthenticator> mpAuth;
QPointer<dlgNotepad> mpNotePad;
// Controls how sent commands are displayed on the main TConsole:
enum class CommandEchoMode {
Never = 0, // Never show sent commands regardless of script preferences
ScriptControl = 1, // Let scripts control via send() wantPrint parameter (default)
Always = 2 // Always show sent commands regardless of script preferences
};
CommandEchoMode mCommandEchoMode = CommandEchoMode::ScriptControl;
/*
* This is set when the Server is remote echoing what WE send to it,
* it will have negotiated the ECHO suboption by sending a IAC WILL ECHO and
* we have agreed to it with a IAC DO ECHO - in this state it is normal
* Telnet behaviour to NOT echo what we send - to prevent doubled lines.
* The rationale behind this is so that when we type passwords - WE do not
* echo what we type and rely on the other end to, but they do not, so as to
* hide them on our screen (and from logging!) - It should negate the effect
* of the above mPrintCommand being true...
*/
bool mIsRemoteEchoingActive = false;
// Command echo mode getters and setters
CommandEchoMode getCommandEchoMode() const { return mCommandEchoMode; }
void setCommandEchoMode(CommandEchoMode mode) { mCommandEchoMode = mode; }
// Backward compatibility methods - for existing code that expects boolean behavior
bool getPrintCommand() const { return mCommandEchoMode != CommandEchoMode::Never; }
void setPrintCommand(bool print) { mCommandEchoMode = print ? CommandEchoMode::ScriptControl : CommandEchoMode::Never; }
public:
void setRemoteEchoingActive(bool active);
bool isRemoteEchoingActive() const { return mIsRemoteEchoingActive; }
// To cover the corner case of the user changing the mode
// while a log is being written, this stores the mode of
// the current log file and is set from
// mIsNextLogFileInHtmlFormat at the point that a log is started.
bool mIsCurrentLogFileInHtmlFormat = false;
// To cover the corner case of the user changing the mode
// while a log is being written, this stores the mode of
// future logs file as set in the profile preferences. See
// also mIsCurrentLogFileInHtmlFormat.
bool mIsNextLogFileInHtmlFormat = false;
bool mIsLoggingTimestamps = false;
// Where to put HTML/text logfile (default is the "Logs" under the profile's
// one):
QString mLogDir;
// A user entered name - if blank a language specific default is used:
QString mLogFileName;
// The first argument to QDateTime::toString(...) to generate a date/time
// dependent filename unless it is empty in which case the above value is
// used - the previously used value of "yyyy-MM-dd#hh-mm-ss" has been
// changed to "yyyy-MM-dd#HH-mm-ss" as we always want a 24-hour clock.
QString mLogFileNameFormat = QLatin1String("yyyy-MM-dd#HH-mm-ss");
bool mResetProfile = false;
int mScreenHeight = 25;
int mScreenWidth = 90;
// has the profile save data been loaded without issues?
// if there were issues during loading, we should not save anything on close
bool mLoadedOk = false;
QString mProfileLoadError;
int mTimeout = 60;
QString mUrl;
QString mBackupHostName;
int mBackupPort = 23;
QString mBackupUrl;
bool mUSE_FORCE_LF_AFTER_PROMPT = false;
bool mUSE_IRE_DRIVER_BUGFIX = false;
bool mUSE_UNIX_EOL = false;
int mWrapAt = 100;
int mWrapIndentCount = 0;
int mWrapHangingIndentCount = 0;
// Rejoin lines that the game hard-wrapped itself so that triggers see the
// whole logical line and Mudlet's own wrapping (mWrapAt) handles display:
bool mUndoServerWrap = false;
int mUndoServerWrapWidth = 80;
// The one-time "this game seems to wrap its own lines" hint was shown:
bool mServerWrapHintShown = false;
int mConsoleBufferSize = 100000;
bool mUseMaxConsoleBufferSize = false;
bool mEditorAutoComplete = true;
// code editor theme for light mode (human-friendly name)
QString mEditorTheme = QLatin1String("Mudlet");
// code editor theme file for light mode on disk for edbee to load
QString mEditorThemeFile = QLatin1String("Mudlet.tmTheme");
// code editor theme for dark mode (human-friendly name), auto-populated on first dark mode switch
QString mEditorThemeDark;
// code editor theme file for dark mode on disk for edbee to load
QString mEditorThemeFileDark;
QString getEditorTheme() const;
QString getEditorThemeFile() const;
void editorThemeChanged();
// search engine URL prefix to search query
QMap<QString, QString> mSearchEngineData;
QMap<QString, QMap<QString, QString>> mPackageInfo;
QMap<QString, QMap<QString, QString>> mModuleInfo;
QString mSearchEngineName;
// trigger/alias/script/etc ID whose Lua code to show when previewing a theme
// remembering this value to show what the user has selected does have its
// flaws in case of items getting created/deleted, but this is just a
// convenience feature and if it gets the item wrong, it's no worse
// than the feature not being there.
int mThemePreviewItemID = -1;
// the type of item (a trigger, an alias, etc) that's previewed
QString mThemePreviewType;
QColor mBlack{QColorConstants::Black};
QColor mLightBlack{QColorConstants::DarkGray};
QColor mRed{QColorConstants::DarkRed};
QColor mLightRed{QColorConstants::Red};
QColor mLightGreen{QColorConstants::Green};
QColor mGreen{QColorConstants::DarkGreen};
QColor mLightBlue{QColorConstants::Blue};
QColor mBlue{QColorConstants::DarkBlue};
QColor mLightYellow{QColorConstants::Yellow};
QColor mYellow{QColorConstants::DarkYellow};
QColor mLightCyan{QColorConstants::Cyan};
QColor mCyan{QColorConstants::DarkCyan};
QColor mLightMagenta{QColorConstants::Magenta};
QColor mMagenta{QColorConstants::DarkMagenta};
QColor mLightWhite{QColorConstants::White};
QColor mWhite{QColorConstants::LightGray};
QColor mFgColor{QColorConstants::LightGray};
QColor mBgColor{QColorConstants::Black};
QColor mCommandBgColor{QColorConstants::Black};
QColor mCommandFgColor{QColor(113, 113, 0)};
QColor mBlack_2{QColorConstants::Black};
QColor mLightBlack_2{QColorConstants::DarkGray};
QColor mRed_2{QColorConstants::DarkRed};
QColor mLightRed_2{QColorConstants::Red};
QColor mLightGreen_2{QColorConstants::Green};
QColor mGreen_2{QColorConstants::DarkGreen};
QColor mLightBlue_2{QColorConstants::Blue};
QColor mBlue_2{QColorConstants::DarkBlue};
QColor mLightYellow_2{QColorConstants::Yellow};
QColor mYellow_2{QColorConstants::DarkYellow};
QColor mLightCyan_2{QColorConstants::Cyan};
QColor mCyan_2{QColorConstants::DarkCyan};
QColor mLightMagenta_2{QColorConstants::Magenta};
QColor mMagenta_2{QColorConstants::DarkMagenta};
QColor mLightWhite_2{QColorConstants::White};
QColor mWhite_2{QColorConstants::LightGray};
QColor mFgColor_2{QColorConstants::LightGray};
QColor mBgColor_2{QColorConstants::Black};
QColor mLowerLevelColor{QColorConstants::DarkGray};
QColor mUpperLevelColor{QColorConstants::White};
QColor mRoomBorderColor{QColorConstants::LightGray};
QColor mRoomCollisionBorderColor{QColorConstants::Yellow};
QColor mMapGridColor{QColor(211, 211, 211, 64)};
QColor mMapInfoBg = QColor(150, 150, 150, 120);
bool mMapStrongHighlight = false;
QStringList mGMCP_merge_table_keys;
bool mLogStatus = false;
bool mTimeStampStatus = false;
bool mEnableSpellCheck = true;
QStringList mInstalledPackages;
// module name = location on disk, sync to other profiles?, priority
QMap<QString, QStringList> mInstalledModules;
// modules that loaded successfully - used to prevent saving modules that failed to load
QSet<QString> mModulesLoadedOk;
// module name = priority
QMap<QString, int> mModulePriorities;
// module name = location on disk, sync to other profiles?, priority
QMap<QString, QStringList> modulesToWrite;
// module name = {"helpURL" = custom link}
QMap<QString, QMap<QString, QString>> moduleHelp;
// Controls how Discord Rich Presence behaves:
// DiscordDisabled - RPC shut down, server unaware
// DiscordShowMudletOnly - shows "Playing Mudlet", server unaware, Lua can still set fields
// DiscordShowGameDetails - full integration with server GMCP/MSDP
DiscordMode mDiscordMode = DiscordShowGameDetails;
// Discord privacy options to give the user control over what data a Server
// can set over OOB protocols (MSDP & GMCP):
DiscordOptionFlags mDiscordAccessFlags{DiscordSetSubMask};
double mLineSize = 10.0;
double mRoomBorderSize = 10.0;
double mRoomSize = 0.5;
double mMapGridLineSize = 0.5;
QSet<QString> mMapInfoContributors;
bool mBubbleMode = false;
bool mMapViewOnly = true;
bool mShowRoomID = false;
bool mShowPanel = true;
bool mShow3DView = false;
QString mServerGUI_Package_version = QLatin1String("-1");
QString mServerGUI_Package_name = QLatin1String("nothing");
bool mAcceptServerGUI = true;
bool mAcceptServerMedia = true;
QColor mCommandLineFgColor{Qt::darkGray};
QColor mCommandLineBgColor{Qt::black};
bool mMapperUseAntiAlias = true;
bool mMapperShowRoomBorders = true;
bool mMapperShowGrid = false;
// Center the map on an area as a whole when it fits entirely in the
// viewport, instead of following the player room. Off by default;
// configurable via the mapCenterSmallAreas key in Mudlet.ini.
bool mMapperCenterSmallAreas = false;
bool mVersionInTTYPE = false;
QSet<QChar> mDoubleClickIgnore;
bool mEnableTextAnalyzer = false;
bool mWritingHostAndModules = false;
// Set from profile preferences, if the timer interval is less
// than this then the normal reoccuring debug output of the entire command
// and script for any timer with a timeout LESS than this is NOT shown
// - this is so the spammy output from short timeout timers can be
// suppressed.
// An invalid/null value is treated as the "show all"/inactive case:
QTime mTimerDebugOutputSuppressionInterval;
std::unique_ptr<QNetworkProxy> mpConnectionProxy;
QString mProfileStyleSheet;
dlgTriggerEditor::SearchOptions mSearchOptions = dlgTriggerEditor::SearchOptionNone;
TConsole::SearchOptions mBufferSearchOptions = TConsole::SearchOption::SearchOptionNone;
QPointer<dlgIRC> mpDlgIRC;
QPointer<MMCPServer> mMMCPServer;
QPointer<dlgProfilePreferences> mpDlgProfilePreferences;
QList<QString> mDockLayoutChanges;
QList<QPointer<TToolBar>> mToolbarLayoutChanges;
// string list: 0 - event name, 1 - display label, 2 - tooltip text
QMap<QString, QStringList> mConsoleActions;
std::map<QString, std::unique_ptr<QKeySequence>> profileShortcuts;
bool mTutorialForCompactLineAlreadyShown = false;
bool mAnnounceIncomingText = true;
bool mAdvertiseScreenReader = false;
bool mEnableClosedCaption = false;
// Turning this off both ignores incoming OSC 8 sequences and advertises 0
// for them, so a server can fall back to MXP or plain text rather than
// sending links Mudlet will not render. It is checked as sequences are
// decoded, so links already drawn in the buffer stay clickable.
bool mEnableOSC8Hyperlinks = true;
enum class BlankLineBehaviour { Show, Hide, ReplaceWithSpace };
Q_ENUM(BlankLineBehaviour)
BlankLineBehaviour mBlankLineBehaviour = BlankLineBehaviour::Show;
// shortcuts options visually impaired players have to switch between the input line and the main window
enum class CaretShortcut { None, Tab, CtrlTab, F6 };
Q_ENUM(CaretShortcut)
// shortcut to switch between the input line and the main window
CaretShortcut mCaretShortcut = CaretShortcut::None;
// stops all triggers/aliases/everything from running
bool mEmergencyStop = false;
signals:
// Tells TTextEdit instances for this profile how to draw the ambiguous
// width characters:
void signal_changeIsAmbigousWidthGlyphsToBeWide(bool);
// Tells TTextEdit instances for this profile whether to animate blinking text:
void signal_changeEnableBlinkText(bool);
void profileSaveStarted();
void profileSaveFinished();
void signal_changeSpellDict(const QString&);
// To tell all TConsole's upper TTextEdit panes to report all Codepoint
// problems as they arrive as well as a summary upon destruction:
void signal_changeDebugShowAllProblemCodepoints(const bool);
// Tells all consoles associated with this Host (but NOT the Central Debug
// one) to change the way they show control characters:
void signal_controlCharacterHandlingChanged(const ControlCharacterMode);
// Tells all command lines to save their history:
void signal_saveCommandLinesHistory();
void mmcpChatNameChanged(const QString&);
void signal_editorThemeChanged();
void signal_remoteEchoChanged(bool enabled);
void signal_forceMXPProcessorOnChanged(bool enabled);
// The frontend (TMainConsole) owns the dialogs these drive; the strings are
// built here so they stay in Host's translation context.
void signal_showMapperScriptReminder();
void signal_showUnpackingProgress(const QString& message, const QString& title);
void signal_hideUnpackingProgress();
private slots:
void slot_purgeTemps();
void slot_saveProfileAfterPackageChange();
private:
void setBorders(const QMargins);
void installPackageFonts(const QString& packageName);
void processGMCPDiscordStatus(const QJsonObject& discordInfo);
void processGMCPDiscordInfo(const QJsonObject& discordInfo);
void loadSecuredPassword();
void removeAllNonPersistentStopWatches();
void updateConsolesFont();
void thankForUsingPTB();
void toggleMapperVisibility();
void createMapper(const bool);
void removePackageInfo(const QString& packageName, const bool);
static void createModuleBackup(const QString& filename, const QString& saveName);
// A single module queued to be written out during a profile save. Its XML document
// is built on the main thread (XMLexport::writeModuleXML()); serializing it to disk
// is deferred to a background task. The job is a complete, self-contained order:
// its own copy of the document plus every path the write needs, so it holds nothing
// whose lifetime the Host controls. It has to: a close that answers "No" to "Save
// profile?", and one that finds the main console already gone, wait for nothing, so
// the Host can be destroyed while the write is still queued.
struct ModuleWriteJob
{
std::shared_ptr<pugi::xml_document> document;
QString moduleName;
QString filename;
QString xmlFilename;
// Empty when this save is not to be backed up first.
QString backupName;
};
// Main thread only: builds every to-be-synced module's XML document, registers its
// writer in `writers`, resolves every path and creates the directories the write
// needs, returning the jobs a background task should serialize.
QList<ModuleWriteJob> prepareModuleSaves(bool backup);
// Writes the prepared module documents (and updates their zips) to disk. Static on
// purpose: it must keep working after the Host that ordered it has been destroyed,
// so it may not reach for any member. Usually a thread pool task, but a waiter in
// waitForProfileSave() can steal it onto the main thread, so it must suit either.
static void writeModuleFiles(const QList<ModuleWriteJob>& jobs);
static void updateModuleZip(const ModuleWriteJob& job);
// Main thread only: snapshot of the still-pending profile-save futures, so a
// background task never reads `writers`/`saveFutures` while the main thread mutates
// them (that concurrent access is a heap-corrupting data race).
QList<QFuture<bool>> pendingXmlSaveFutures() const;
void waitForAsyncXmlSave();
void reloadModules();
void startMapAutosave(const int interval);
void timerEvent(QTimerEvent* event) override;
void autoSaveMap();
QString sanitizePackageName(const QString packageName) const;
TCommandLine* activeCommandLine();
void closeChildren();
void setupSandboxedLuaState(lua_State* L);
QStringList mModulesToSync;
QScopedPointer<LuaInterface> mLuaInterface;
// Experiment system storage: key -> enabled state
QMap<QString, bool> mExperiments;
// Static whitelist of valid experiments
static const QSet<QString> mValidExperiments;
TriggerUnit mTriggerUnit;
TimerUnit mTimerUnit;
ScriptUnit mScriptUnit;
AliasUnit mAliasUnit;
ActionUnit mActionUnit;
KeyUnit mKeyUnit;
GifTracker mGifTracker;
// The profile save that a package/module install or uninstall owes is put off
// to the next event loop pass, so that the asynchronous save mechanism is not
// asked to write out something that was just taken out of memory. Restarting
// this timer also folds a batch of installs/uninstalls into a single save.
// It has to be a member timer rather than a QTimer::singleShot(): a call
// queued on the Host is still delivered after the Host has been destroyed,
// and the save then reads freed members - closeChildren() and ~Host() stop
// this one instead (#9653).
QTimer mDeferredSaveTimer;
QFile mErrorLogFile;
QMap<QString, TEvent*> mEventMap;
int mHostID;
QString mHostName;
QString mDiscordGameName; // Discord self-reported game name
bool mIsClosingDown = false;
QString mLine;
QString mLogin;
QString mPass;
int mPort;
// Reset to false whenever a connection is (re)established; set true by Host::send() on the first
// user/script command. See userSentInputThisConnection().
bool mUserSentInputThisConnection = false;
int mRetries = 5;
bool mSaveProfileOnExit = false;
// To keep things simple for Lua the first stopwatch will be allocated a key
// of 1 - and anything less that that will be rejected - and we force
// createStopWatch() to return 0 during script loading so that we do not get
// superious stopwatches from being created then (when
// mIsProfileLoadingSequence is true):
std::map<int, std::unique_ptr<stopWatch>> mStopWatchMap;
QMap<QString, QStringList> mAnonymousEventHandlerFunctions;
QStringList mActiveModules;
bool mHaveMapperScript = false;
// This option makes the control on the preferences tristated so the value
// used depends - currently - on what the MUD Server encoding is (only set
// true for GBK, GB18030, Big5/Big-HKCS, EUC-KR ones) - however this was
// due for revision once locale/language support is brought in - when it
// could be made dependent on that instead.
bool mAutoAmbigousWidthGlyphsSetting = true;
// If above is true is the value deduced from the MUD server encoding, if
// the above is false is the user's direct setting - this is so that changes
// in the TTextEdit classes are only made when necessary:
bool mWideAmbigousWidthGlyphs = false;
// Whether to animate blinking text (SGR codes 5 and 6) with a pulse effect
bool mEnableBlinkText = false;
// keeps track of all of the array writers we're currently operating with
QHash<QString, std::shared_ptr<XMLexport>> writers;
QFuture<void> mModuleFuture;
// Will be null/empty if is to use Mudlet's default/own presence
QString mDiscordApplicationID;
// Will be null/empty if they have not set their own invite
QString mDiscordInviteURL;
// If non-empty, Rich Presence will only be shown when logged into this
// Discord username - useful if multiple people share a machine.
QString mRequiredDiscordUserName;
QString mMMCPChatName;
QString mMMCPChatPrefix;
quint16 mMMCPChatPort;
bool mMMCPAutostartServer;
bool mMMCPAllowPeekRequests;
bool mMMCPPrefixEmotes;
bool mMMCPAddChatMessageNewline;
bool mMMCPAutoAcceptCalls;
bool mMMCPShowSnoopInMainConsole;
// Handles whether to treat 16M-Colour ANSI SGR codes which only use
// semi-colons as separator have the initial Colour Space Id parameter
// (true) or not (false):
bool mSGRCodeHasColSpaceId = false;
// Flag whether the Server can use ANSI OSC "P#RRGGBB\" to redefine the
// 16 basic colors (and OSC "R\" to reset them).
bool mServerMayRedefineColors = false;
// Was public but hidden to prevent it being changed without going through
// the process to signal to users that they need to change dictionaries:
QString mSpellDic;
// These are hidden to prevent them being changed directly, they are also
// mirrored/cached in the main TConsole's instance so they do not need to be
// looked up directly by that class:
bool mEnableUserDictionary = true;
bool mUseSharedDictionary = false;
// These hold values that are needed in the TMap class which are saved with
// the profile - but which cannot be kept there as that class is not
// necessarily instantiated when the profile is read.
// Base color(s) for the player room in the mappers:
// Mode selected:
// 0 is closest to original style with adjustable outer diameter
// 1 is Fixed red color ring with adjustable outer/inner diameter
// 2 is fixed blue/yellow colors ring with adjustable outer/inner diameter
// 3 is adjustable outer(primary)/inner(secondary) colors ring with adjustable outer/inner diameter
quint8 mPlayerRoomStyle = 0;
QColor mPlayerRoomOuterColor{Qt::red};
QColor mPlayerRoomInnerColor{Qt::white};
// Percentage of the room size (actually width) for the outer diameter of
// the circular marking, integer percentage clamped in the preferences
// between 200 and 50:
quint8 mPlayerRoomOuterDiameterPercentage = 120;
// Percentage of the outer size for the inner diameter of the circular
// marking, integer percentage clamped in the preferences between 83 and 0.
// NOT USED FOR "Original" style marking (the 0'th one):
quint8 mPlayerRoomInnerDiameterPercentage = 70;
// Whether the TTextEditor class should immediately report to debug output
// any dodgy codepoints that it has problems with - if false it only reports
// each codepoint the first time it encounters itL
bool mDebugShowAllProblemCodepoints = false;
// Now a per profile option this one represents the state of this profile:
bool mCompactInputLine = false;
QTimer purgeTimer;
// How to display (most) incoming control characters in TConsoles:
// ControlCharacterMode::AsIs (0x0) = as is, no replacement
// ControlCharacterMode::Picture (0x1) = as Unicode "Control
// Pictures" - use Unicode codepoints in range U+2400 to U+2421
// ControlCharacterMode::OEM (0x2) = as "OEM Font"
// characters (most often seen as a part of CP437
// encoding), see the corresponding Wikipedia page, e.g.
// EN: https://en.wikipedia.org/wiki/Code_page_437
// DE: https://de.wikipedia.org/wiki/Codepage_437
// RU: https://ru.wikipedia.org/wiki/CP437
ControlCharacterMode mControlCharacter = ControlCharacterMode::AsIs;
bool mLargeAreaExitArrows = false;
bool mEditorShowBidi = true;
// should focus should be on the main window with the caret enabled?
bool mCaretEnabled = false;
// Tracks which command line was last used for this profile so that we can
// return to it when switching between profiles:
QStack<QPointer<TCommandLine>> mpLastCommandLineUsed;
// ensures that only one "zero-time" timer is created by the lambda in
// setFocusOnHostActiveCommandLine(), even when it is called multiple
// times:
bool mFocusTimerRunning = false;
QMargins mBorders;
QMargins mUserBorders;
QMargins mMxpBorders;
// The range - applied to ALL command lines - is 0 to 10000, with the knob
// on the profile preferences having a log-step action with multiples
// of 10 to integer powers and steps of (0,) 10, 20, 50, 100. Prior to the
// introduction of this feature the control would effectively have been
// zero - and whilst the knob shows the special value of "None" then
// to reproduce that behavior there is little reason to not enable it
// by default:
int mCommandLineHistorySaveSize = 500;
// Whether to display each item's ID number in the editor:
bool mShowIDsInEditor = false;
// Whether F3 search functionality is enabled
bool mF3SearchEnabled = false;
// Whether to force the MXP processor to be on, even if not negotiated with the
// MUD Server
bool mForceMXPProcessorOn = false;
// Set when the mudlet singleton demands that we close - used to force an
// attempt to save the profile and map - without asking:
bool mForcedClose = false;
};
Q_DECLARE_OPERATORS_FOR_FLAGS(Host::DiscordOptionFlags)
#endif // MUDLET_HOST_H