mudlet/src/TLuaInterpreter.cpp
Vadim Peretokin b1fc20c19d
fix: three Lua API bugs the core specs found - command history, phantom profiles, package names (#9816)
#### Brief overview of PR changes/additions

- `setSaveCommandHistory()` and `setSaveCommandHistory(name)` turn
saving on instead of raising: the argument count was tested one too
high, which also made the branch reading a boolean after a name
unreachable.
- `setProfileInformation()` / `clearProfileInformation()` refuse a
profile that is not there instead of conjuring one.
`mudlet::writeProfileData()` creates whatever folder it is handed, so an
unknown name used to leave a phantom in `getProfiles()` and the
connection dialog. The check went into the two Lua entry points because
the connection dialog needs that folder creation to save a character
name for a default game (#8101), and a game Mudlet ships with only
counts once it has a folder of its own.
- `verbosePackageInstall()` names the package rather than its whole
path: the profile folder was stripped with `gsub`, which reads a path as
a Lua pattern, and the `-` in "Mudlet self-test" (or in any of the many
real paths holding one) never matches.

#### Motivation for adding to Mudlet

All three were found and filed while writing the core and package specs
merged in #9802, where they were left as `pending()`. Those specs are
flipped to real assertions here, so each fix is pinned by a test that
fails without it.

#### Test case

`lua setSaveCommandHistory()`, `lua setProfileInformation("no such
profile", "x")` then `lua display(getProfiles())`, and `lua
installPackage("https://.../thing.mpackage")` from a profile whose path
holds a `-`.

Closes #9803
Closes #9804
Closes #9806

Assisted-by: Claude:claude-opus-5

#### Other info

The red ubuntu and windows legs are the known development-side
`ProfileLifecycleTest` failure, not this PR: development @ e42bd1e28
fails the same `initTestCase()` assertion (run 31464046946). A dedicated
PR fixes it.
2026-08-12 11:30:56 +02:00

8708 lines
373 KiB
C++

/***************************************************************************
* Copyright (C) 2008-2013 by Heiko Koehn - KoehnHeiko@googlemail.com *
* Copyright (C) 2013-2023, 2025-2026 by Stephen Lyons *
* - slysven@virginmedia.com *
* Copyright (C) 2014-2017 by Ahmed Charles - acharles@outlook.com *
* Copyright (C) 2016 by Eric Wallace - eewallace@gmail.com *
* Copyright (C) 2016 by Chris Leacy - cleacy1972@gmail.com *
* Copyright (C) 2016-2018 by Ian Adkins - ieadkins@gmail.com *
* Copyright (C) 2017 by Chris Reid - WackyWormer@hotmail.com *
* Copyright (C) 2022-2025 by Lecker Kebap - Leris@mudlet.org *
* *
* 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 "TLuaInterpreter.h"
#include "EAction.h"
#include "Host.h"
#include "TAlias.h"
#include "TCommandLine.h"
#include "TConsole.h"
#include "TDebug.h"
#include "TEvent.h"
#include "TFlipButton.h"
#include "TForkedProcess.h"
#include "TGameDetails.h"
#include "TLabel.h"
#include "TMap.h"
#include "TMapLabel.h"
#include "TRoomDB.h"
#include "TTextEdit.h"
#include "TEncodingHelper.h"
#include "TTimer.h"
#include "dlgComposer.h"
#include "dlgIRC.h"
#include "dlgMapper.h"
#include "dlgModuleManager.h"
#include "dlgTriggerEditor.h"
#include "mudlet.h"
#if defined(INCLUDE_3DMAPPER)
#include "glwidget_integration.h"
#endif
#include <math.h>
#include <QtConcurrentRun>
#include <QApplication>
#include <QCollator>
#include <QCoreApplication>
#include <QDesktopServices>
#include <QSettings>
#if defined(Q_OS_MACOS)
// Only used for this OS:
#include <QStandardPaths>
#endif
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonParseError>
#include <QSaveFile>
#include <QTemporaryDir>
#include <QTemporaryFile>
#include <QTextStream>
#include <QFileInfo>
#include <QVector>
#include <limits>
using namespace std::chrono_literals;
extern "C" {
int luaopen_yajl(lua_State*);
}
// No documentation available in wiki - internal function
static bool isMain(const QString& name)
{
if (name.isEmpty()) {
return true;
}
if (!name.compare(qsl("main"))) {
return true;
}
return false;
}
static const char* bad_cmdline_type = "%s: bad argument #%d type (command line name as string expected, got %s)!";
static const char* bad_cmdline_value = "command line \"%s\" not found";
const QString TLuaInterpreter::csmInvalidRoomID{qsl("number %1 is not a valid roomID")};
const QString TLuaInterpreter::csmInvalidStopWatchID{qsl("stopwatch with ID %1 not found")};
const QString TLuaInterpreter::csmInvalidRedValue{qsl("red value %1 needs to be between 0-255")};
const QString TLuaInterpreter::csmInvalidGreenValue{qsl("green value %1 needs to be between 0-255")};
const QString TLuaInterpreter::csmInvalidBlueValue{qsl("blue value %1 needs to be between 0-255")};
const QString TLuaInterpreter::csmInvalidAlphaValue{qsl("alpha value %1 needs to be between 0-255")};
const QString TLuaInterpreter::csmInvalidExitRoomID{qsl("number %1 is not a valid exit roomID")};
const QString TLuaInterpreter::csmInvalidItemID{qsl("item ID as %1 does not seem to be parseable as a positive integer")};
const QString TLuaInterpreter::csmInvalidAreaID{qsl("number %1 is not a valid area id")};
const QString TLuaInterpreter::csmInvalidAreaName{qsl("string '%1' is not a valid area name")};
#define CMDLINE_NAME(ARG_L, ARG_pos) \
({ \
int pos_ = (ARG_pos); \
if (!lua_isstring(ARG_L, pos_)) { \
lua_pushfstring(ARG_L, bad_cmdline_type, __FUNCTION__, pos_, luaL_typename(ARG_L, pos_)); \
return lua_error(ARG_L); \
} \
lua_tostring(ARG_L, pos_); \
})
#define COMMANDLINE(ARG_L, ARG_name) \
({ \
const QString& name_ = (ARG_name); \
auto console_ = getHostFromLua(ARG_L).mpConsole; \
auto cmdLine_ = isMain(name_) ? &*console_->mpCommandLine : console_->mSubCommandLineMap.value(name_); \
if (!cmdLine_) { \
lua_pushnil(ARG_L); \
lua_pushfstring(ARG_L, bad_cmdline_value, name_.toUtf8().constData()); \
return 2; \
} \
cmdLine_; \
})
// variable names within these macros have trailing underscores because in
// at least one case, masking an existing variable with the new one confused
// GCC, leading to a crash.
TLuaInterpreter::TLuaInterpreter(Host* pH, const QString& hostName, int id)
: mpHost(pH)
, hostName(hostName)
, mHostID(id)
, purgeTimer(this)
, mpFileDownloader(new QNetworkAccessManager(this))
, mpFileSystemWatcher(new QFileSystemWatcher(this))
{
connect(&purgeTimer, &QTimer::timeout, this, &TLuaInterpreter::slot_purge);
connect(mpFileDownloader, &QNetworkAccessManager::finished, this, &TLuaInterpreter::slot_httpRequestFinished);
connect(mpFileSystemWatcher, &QFileSystemWatcher::fileChanged, this, &TLuaInterpreter::slot_pathChanged);
connect(mpFileSystemWatcher, &QFileSystemWatcher::directoryChanged, this, &TLuaInterpreter::slot_pathChanged);
initLuaGlobals();
purgeTimer.start(2s);
}
TLuaInterpreter::~TLuaInterpreter()
{
lua_close(pGlobalLua);
}
// No documentation available in wiki - internal function
// Replaces a check like this:
// if (!lua_isboolean(L, 14)) {
// lua_pushfstring(L,
// "createMapLabel: bad argument #14 type (showOnTop as boolean is optional, got %s!)",
// luaL_typename(L, 14));
// return lua_error(L);
// }
// bool showOnTop = lua_toboolean(L, 14);
//
// With reduced repetition like that:
// bool showOnTop = getVerifiedBool(L, "createMapLabel", 14, "showOnTop", true);
//
// The "isOptional" parameter is optional but modifies the error message to say
// that an argument is optional and it will default to not-optional parameters!
// HOWEVER it does not actually handle the absence of an argument that is
// supposed to BE optional - that has to be done by the caller before it
// makes the call... 8-P
//
// See also: getVerifiedString, getVerifiedInt, getVerifiedFloat, errorArgumentType
bool TLuaInterpreter::getVerifiedBool(lua_State* L, const char* functionName, const int pos, const char* publicName, const bool isOptional)
{
if (!checkBoolArg(L, functionName, pos, publicName, isOptional)) {
lua_error(L);
Q_UNREACHABLE();
}
return lua_toboolean(L, pos);
}
// No documentation available in wiki - internal function
// The non-raising counterpart of getVerifiedBool - see checkStringArg()
bool TLuaInterpreter::checkBoolArg(lua_State* L, const char* functionName, const int pos, const char* publicName, const bool isOptional)
{
if (!lua_isboolean(L, pos)) {
errorArgumentType(L, functionName, pos, publicName, "boolean", isOptional);
return false;
}
return true;
}
// No documentation available in wiki - internal function
// See also: getVerifiedBool
/*static*/ bool TLuaInterpreter::checkStringOrIntegerArg(lua_State* L, const char* functionName, const int pos, const char* publicName, const bool isOptional)
{
if (lua_type(L, pos) != LUA_TNUMBER && lua_type(L, pos) != LUA_TSTRING) {
errorArgumentType(L, functionName, pos, publicName, "string or integer", isOptional);
return false;
}
return true;
}
// No documentation available in wiki - internal function
// See also: getVerifiedBool
/*static*/ std::pair<bool, QString> TLuaInterpreter::getVerifiedStringOrInteger(lua_State* L, const char* functionName, const int pos, const char* publicName, const bool isOptional)
{
if (!checkStringOrIntegerArg(L, functionName, pos, publicName, isOptional)) {
lua_error(L);
Q_UNREACHABLE();
}
if (lua_type(L, pos) == LUA_TNUMBER) {
// use lua_tonumber(...) and round because lua_tointeger(...) can return
// oversized values (long long int?) on Windows which do not always fit
// into an int:
return {true, QString::number(qRound(lua_tonumber(L, pos)))};
}
return {false, lua_tostring(L, pos)};
}
// No documentation available in wiki - internal function
// Leaves the "bad argument" message on the Lua stack instead of raising, so the
// caller can raise it with `return lua_error(L)` at a point of its choosing.
// lua_error() longjmps past C++ destructors: any QString already built from an
// earlier argument would have its buffer stranded, so callers taking more than
// one string check every argument here first - in argument order, so the same
// failure is still the one reported - and only then build the QStrings.
// See also: getVerifiedString, reportInvalidLuaCodeParam
bool TLuaInterpreter::checkStringArg(lua_State* L, const char* functionName, const int pos, const char* publicName, const bool isOptional)
{
if (!lua_isstring(L, pos)) {
errorArgumentType(L, functionName, pos, publicName, "string", isOptional);
return false;
}
return true;
}
// No documentation available in wiki - internal function
// See also: getVerifiedBool
QString TLuaInterpreter::getVerifiedString(lua_State* L, const char* functionName, const int pos, const char* publicName, const bool isOptional)
{
if (!checkStringArg(L, functionName, pos, publicName, isOptional)) {
lua_error(L);
Q_UNREACHABLE();
}
return lua_tostring(L, pos);
}
// No documentation available in wiki - internal function
// The non-raising counterpart of getVerifiedInt - see checkStringArg()
bool TLuaInterpreter::checkIntArg(lua_State* L, const char* functionName, const int pos, const char* publicName, const bool isOptional)
{
if (!lua_isnumber(L, pos)) {
errorArgumentType(L, functionName, pos, publicName, "number", isOptional);
return false;
}
// lua_tointeger(...) returns a ptrdiff_t which on 64-bit platforms is a
// signed 64 bit value, which is usually larger than an "int" a.k.a. an
// int32_t:
// We have to error out here otherwise we have to restructure every usage
// to handle such an over/under-flow - at least with a change to a
// std::optional<int>...
auto const result = lua_tointeger(L, pos);
if (result < std::numeric_limits<int>::min() || result > std::numeric_limits<int>::max()) {
lua_pushfstring(L,
"%s: integer over/under-flow in argument #%d (%s as an integer, provided value %s is outside of valid range %d to %d!)",
functionName,
pos,
publicName,
lua_tostring(L, pos),
std::numeric_limits<int>::min(),
std::numeric_limits<int>::max());
return false;
}
return true;
}
// No documentation available in wiki - internal function
// See also: getVerifiedBool
int TLuaInterpreter::getVerifiedInt(lua_State* L, const char* functionName, const int pos, const char* publicName, const bool isOptional)
{
if (!checkIntArg(L, functionName, pos, publicName, isOptional)) {
lua_error(L);
Q_UNREACHABLE();
}
return static_cast<int>(lua_tointeger(L, pos));
}
// No documentation available in wiki - internal function
// The non-raising counterpart of getVerifiedFloat and getVerifiedDouble - see checkStringArg()
bool TLuaInterpreter::checkNumberArg(lua_State* L, const char* functionName, const int pos, const char* publicName, const bool isOptional)
{
if (!lua_isnumber(L, pos)) {
errorArgumentType(L, functionName, pos, publicName, "number", isOptional);
return false;
}
return true;
}
// No documentation available in wiki - internal function
// See also: getVerifiedBool
float TLuaInterpreter::getVerifiedFloat(lua_State* L, const char* functionName, const int pos, const char* publicName, const bool isOptional)
{
if (!checkNumberArg(L, functionName, pos, publicName, isOptional)) {
lua_error(L);
Q_UNREACHABLE();
}
return static_cast<float>(lua_tonumber(L, pos));
}
// No documentation available in wiki - internal function
// See also: getVerifiedBool
double TLuaInterpreter::getVerifiedDouble(lua_State* L, const char* functionName, const int pos, const char* publicName, const bool isOptional)
{
if (!checkNumberArg(L, functionName, pos, publicName, isOptional)) {
lua_error(L);
Q_UNREACHABLE();
}
return lua_tonumber(L, pos);
}
// No documentation available in wiki - internal function
// Raises a Lua error in case of an API usage mistake
// See also: getVerifiedBool, warnArgumentValue
void TLuaInterpreter::errorArgumentType(lua_State* L, const char* functionName, const int pos, const char* publicName, const char* publicType, const bool isOptional)
{
if (isOptional) {
lua_pushfstring(L, "%s: bad argument #%d type (%s as %s is optional, got %s!)", functionName, pos, publicName, publicType, luaL_typename(L, pos));
} else {
lua_pushfstring(L, "%s: bad argument #%d type (%s as %s expected, got %s!)", functionName, pos, publicName, publicType, luaL_typename(L, pos));
}
}
// No documentation available in wiki - internal function
// returns nil+msg in case of a data mistake, for example a missing room. Should not raise a Lua error
// See also: announceWrongArgumentType
int TLuaInterpreter::warnArgumentValue(lua_State* L, const char* functionName, const QString& message, const bool useFalseInsteadofNil)
{
if (Q_LIKELY(!useFalseInsteadofNil)) {
lua_pushnil(L);
} else {
lua_pushboolean(L, false);
}
lua_pushstring(L, message.toUtf8().constData());
if (mudlet::smDebugMode) {
auto& host = getHostFromLua(L);
TDebug(Qt::white, QColorConstants::Svg::orange) << "Lua: " << functionName << ": " << message << "\n" >> &host;
}
return 2;
}
// No documentation available in wiki - internal function
int TLuaInterpreter::warnArgumentValue(lua_State* L, const char* functionName, const char* message, const bool useFalseInsteadofNil)
{
if (Q_LIKELY(!useFalseInsteadofNil)) {
lua_pushnil(L);
} else {
lua_pushboolean(L, false);
}
lua_pushstring(L, message);
if (mudlet::smDebugMode) {
auto& host = getHostFromLua(L);
TDebug(Qt::white, QColorConstants::Svg::orange) << "Lua: " << functionName << ": " << message << "\n" >> &host;
}
return 2;
}
// No documentation available in wiki - internal function
// Raises additional sysDownloadError Events on failure to process
// the local file, the second argument is "failureToWriteLocalFile" and besides
// the file to be written being the third argument (as multiple downloads are
// supported) a fourth argument gives the local file problem, one of:
// * "unableToOpenLocalFileForWriting"
// * "unableToWriteLocalFile"
// or a QFile::errorString() for the issue at hand
// Upon success we now give an additional (third value) which gives the number
// of bytes written into the downloaded file.
void TLuaInterpreter::slot_httpRequestFinished(QNetworkReply* reply)
{
Host* pHost = mpHost;
if (!pHost) {
qWarning() << qsl("TLuaInterpreter::slot_httpRequestFinished(...) ERROR: NULL Host pointer!");
return;
}
if (reply->error() != QNetworkReply::NoError) {
TEvent event{};
QString localFileName;
switch (reply->operation()) {
case QNetworkAccessManager::PostOperation:
event.mArgumentList << qsl("sysPostHttpError");
event.mArgumentTypeList << ARGUMENT_TYPE_STRING;
event.mArgumentList << reply->errorString();
event.mArgumentTypeList << ARGUMENT_TYPE_STRING;
event.mArgumentList << reply->url().toString();
event.mArgumentTypeList << ARGUMENT_TYPE_STRING;
break;
case QNetworkAccessManager::PutOperation:
event.mArgumentList << qsl("sysPutHttpError");
event.mArgumentTypeList << ARGUMENT_TYPE_STRING;
event.mArgumentList << reply->errorString();
event.mArgumentTypeList << ARGUMENT_TYPE_STRING;
event.mArgumentList << reply->url().toString();
event.mArgumentTypeList << ARGUMENT_TYPE_STRING;
break;
case QNetworkAccessManager::GetOperation:
localFileName = downloadMap.value(reply);
event.mArgumentList << (localFileName.isEmpty() ? qsl("sysGetHttpError") : qsl("sysDownloadError"));
event.mArgumentTypeList << ARGUMENT_TYPE_STRING;
event.mArgumentList << reply->errorString();
event.mArgumentTypeList << ARGUMENT_TYPE_STRING;
if (!localFileName.isEmpty()) {
event.mArgumentList << localFileName;
event.mArgumentTypeList << ARGUMENT_TYPE_STRING;
}
event.mArgumentList << reply->url().toString();
event.mArgumentTypeList << ARGUMENT_TYPE_STRING;
downloadMap.remove(reply);
break;
case QNetworkAccessManager::DeleteOperation:
event.mArgumentList << qsl("sysDeleteHttpError");
event.mArgumentTypeList << ARGUMENT_TYPE_STRING;
event.mArgumentList << reply->errorString();
event.mArgumentTypeList << ARGUMENT_TYPE_STRING;
event.mArgumentList << reply->url().toString();
event.mArgumentTypeList << ARGUMENT_TYPE_STRING;
break;
case QNetworkAccessManager::HeadOperation:
break;
case QNetworkAccessManager::CustomOperation:
event.mArgumentList << qsl("sysCustomHttpError");
event.mArgumentTypeList << ARGUMENT_TYPE_STRING;
event.mArgumentList << reply->errorString();
event.mArgumentTypeList << ARGUMENT_TYPE_STRING;
event.mArgumentList << reply->url().toString();
event.mArgumentTypeList << ARGUMENT_TYPE_STRING;
event.mArgumentList << reply->request().attribute(QNetworkRequest::CustomVerbAttribute).toString();
event.mArgumentTypeList << ARGUMENT_TYPE_STRING;
break;
case QNetworkAccessManager::UnknownOperation:
break;
}
event.mArgumentList << QString::number(createHttpResponseTable(reply));
event.mArgumentTypeList << ARGUMENT_TYPE_TABLE;
reply->deleteLater();
downloadMap.remove(reply);
pHost->raiseEvent(event);
return;
}
handleHttpOK(reply);
}
// No documentation available in wiki - internal function
void TLuaInterpreter::handleHttpOK(QNetworkReply* reply)
{
TEvent event{};
Host* pHost = mpHost;
if (!pHost) {
return;
}
switch (reply->operation()) {
case QNetworkAccessManager::HeadOperation:
break;
case QNetworkAccessManager::DeleteOperation:
event.mArgumentList << qsl("sysDeleteHttpDone");
event.mArgumentTypeList << ARGUMENT_TYPE_STRING;
event.mArgumentList << reply->url().toString();
event.mArgumentTypeList << ARGUMENT_TYPE_STRING;
event.mArgumentList << QString(reply->readAll());
event.mArgumentTypeList << ARGUMENT_TYPE_STRING;
break;
case QNetworkAccessManager::CustomOperation:
event.mArgumentList << QString("sysCustomHttpDone");
event.mArgumentTypeList << ARGUMENT_TYPE_STRING;
event.mArgumentList << reply->url().toString();
event.mArgumentTypeList << ARGUMENT_TYPE_STRING;
event.mArgumentList << QString(reply->readAll());
event.mArgumentTypeList << ARGUMENT_TYPE_STRING;
event.mArgumentList << reply->request().attribute(QNetworkRequest::CustomVerbAttribute).toString();
event.mArgumentTypeList << ARGUMENT_TYPE_STRING;
break;
case QNetworkAccessManager::UnknownOperation:
break;
case QNetworkAccessManager::PostOperation:
event.mArgumentList << qsl("sysPostHttpDone");
event.mArgumentTypeList << ARGUMENT_TYPE_STRING;
event.mArgumentList << reply->url().toString();
event.mArgumentTypeList << ARGUMENT_TYPE_STRING;
event.mArgumentList << QString(reply->readAll());
event.mArgumentTypeList << ARGUMENT_TYPE_STRING;
break;
case QNetworkAccessManager::PutOperation:
event.mArgumentList << qsl("sysPutHttpDone");
event.mArgumentTypeList << ARGUMENT_TYPE_STRING;
event.mArgumentList << reply->url().toString();
event.mArgumentTypeList << ARGUMENT_TYPE_STRING;
event.mArgumentList << QString(reply->readAll());
event.mArgumentTypeList << ARGUMENT_TYPE_STRING;
break;
case QNetworkAccessManager::GetOperation:
const QString localFileName = downloadMap.value(reply);
downloadMap.remove(reply);
// If the user did not give us a file path, we're not going to
// consider this an error, we're just going to attach the reply
// directly. Another way this could happen is the user made a POST
// request, and it redirected to a GET. In the case of POST requests,
// we don't ask the user for a file path.
if (localFileName.isEmpty()) {
event.mArgumentList << QLatin1String("sysGetHttpDone");
event.mArgumentTypeList << ARGUMENT_TYPE_STRING;
event.mArgumentList << reply->url().toString();
event.mArgumentTypeList << ARGUMENT_TYPE_STRING;
event.mArgumentList << QString(reply->readAll());
event.mArgumentTypeList << ARGUMENT_TYPE_STRING;
break;
}
QSaveFile localFile(localFileName);
if (!localFile.open(QFile::WriteOnly)) {
event.mArgumentList << QLatin1String("sysDownloadError");
event.mArgumentTypeList << ARGUMENT_TYPE_STRING;
event.mArgumentList << QLatin1String("Couldn't save to the destination file");
event.mArgumentTypeList << ARGUMENT_TYPE_STRING;
event.mArgumentList << localFileName;
event.mArgumentTypeList << ARGUMENT_TYPE_STRING;
event.mArgumentList << QLatin1String("Couldn't open the destination file for writing (permission errors?)");
event.mArgumentTypeList << ARGUMENT_TYPE_STRING;
break;
}
qint64 const bytesWritten = localFile.write(reply->readAll());
if (bytesWritten == -1) {
event.mArgumentList << QLatin1String("sysDownloadError");
event.mArgumentTypeList << ARGUMENT_TYPE_STRING;
event.mArgumentList << QLatin1String("Couldn't save to the destination file");
event.mArgumentTypeList << ARGUMENT_TYPE_STRING;
event.mArgumentList << localFileName;
event.mArgumentTypeList << ARGUMENT_TYPE_STRING;
event.mArgumentList << QLatin1String("Couldn't write downloaded content into the destination file");
event.mArgumentTypeList << ARGUMENT_TYPE_STRING;
break;
}
if (!localFile.commit()) {
qDebug() << "TTLuaInterpreter::handleHttpOK: error saving downloaded file: " << localFile.errorString();
}
if (localFile.error() == QFile::NoError) {
event.mArgumentList << QLatin1String("sysDownloadDone");
event.mArgumentTypeList << ARGUMENT_TYPE_STRING;
event.mArgumentList << localFileName;
event.mArgumentTypeList << ARGUMENT_TYPE_STRING;
event.mArgumentList << QString::number(bytesWritten);
event.mArgumentTypeList << ARGUMENT_TYPE_NUMBER;
} else {
event.mArgumentList << QLatin1String("sysDownloadError");
event.mArgumentTypeList << ARGUMENT_TYPE_STRING;
event.mArgumentList << QLatin1String("Couldn't save to the destination file");
event.mArgumentTypeList << ARGUMENT_TYPE_STRING;
event.mArgumentList << localFileName;
event.mArgumentTypeList << ARGUMENT_TYPE_STRING;
event.mArgumentList << localFile.errorString();
event.mArgumentTypeList << ARGUMENT_TYPE_STRING;
}
break;
}
event.mArgumentList << QString::number(createHttpResponseTable(reply));
event.mArgumentTypeList << ARGUMENT_TYPE_TABLE;
reply->deleteLater();
pHost->raiseEvent(event);
}
// No documentation available in wiki - internal function
// No documentation available in wiki - internal function
void TLuaInterpreter::slot_pathChanged(const QString& path)
{
// According to QtDocs it is possible that some editors will delete old file and create new one on edits
// Therefore it is required to add file to watches again
if (!mpFileSystemWatcher->files().contains(path) && QFile::exists(path)) {
mpFileSystemWatcher->addPath(path);
}
TEvent event{};
event.mArgumentList << QLatin1String("sysPathChanged");
event.mArgumentTypeList << ARGUMENT_TYPE_STRING;
event.mArgumentList << path;
event.mArgumentTypeList << ARGUMENT_TYPE_STRING;
mpHost->raiseEvent(event);
}
// No documentation available in wiki - internal function
void TLuaInterpreter::slot_deleteSender(int exitCode, QProcess::ExitStatus exitStatus)
{
Q_UNUSED(exitCode)
Q_UNUSED(exitStatus)
objectsToDelete.append(sender());
}
// No documentation available in wiki - internal function
void TLuaInterpreter::slot_purge()
{
while (!objectsToDelete.isEmpty()) {
delete objectsToDelete.takeFirst();
}
}
// No documentation available in wiki - internal function
int TLuaInterpreter::Wait(lua_State* L)
{
const int n = lua_gettop(L);
if (n != 1) {
lua_pushstring(L, "Wait: wrong number of arguments");
return lua_error(L);
}
const int luaSleepMsec = getVerifiedInt(L, __func__, 1, "sleep time in msec");
msleep(luaSleepMsec);
return 0;
}
// No documentation available in wiki - internal function
// dirToString will now catch and validate pretty much any string that could
// be a normal direction in a case insensitive manner and convert it to a
// standard value (one of: "n", "ne", ..., "nw", "up", "down", "in" or
// "out") but leave anything else as entered; OR convert a direction code as
// a number from 1 to 12 to those same standard direction strings.
// This is intended as a temporary step until a uniform means of specifying
// both "normal" and "special" exits for all lua commands that take exit
// directions as arguments in an unambiguous manner can be formulated - though
// to maintain backwards compatibility the current functions will remain even
// if they get marked out as being deprecated.
QString TLuaInterpreter::dirToString(lua_State* L, int position)
{
if (lua_isnumber(L, position)) {
qint64 const dirNum = static_cast<qint64>(lua_tonumber(L, position));
switch (dirNum) {
// breaks not needed - all handled cases end in a return!
case 1:
return qsl("n");
case 2:
return qsl("ne");
case 3:
return qsl("nw");
case 4:
return qsl("e");
case 5:
return qsl("w");
case 6:
return qsl("s");
case 7:
return qsl("se");
case 8:
return qsl("sw");
case 9:
return qsl("up");
case 10:
return qsl("down");
case 11:
return qsl("in");
case 12:
return qsl("out");
default:
return QString();
}
}
if (lua_isstring(L, position)) {
QString direction{lua_tostring(L, position)};
if (!direction.compare(QLatin1String("n"), Qt::CaseInsensitive) || !direction.compare(QLatin1String("north"), Qt::CaseInsensitive)) {
return QLatin1String("n");
} else if (!direction.compare(QLatin1String("e"), Qt::CaseInsensitive) || !direction.compare(QLatin1String("east"), Qt::CaseInsensitive)) { // NOLINT(readability-else-after-return)
return QLatin1String("e");
} else if (!direction.compare(QLatin1String("s"), Qt::CaseInsensitive) || !direction.compare(QLatin1String("south"), Qt::CaseInsensitive)) { // NOLINT(readability-else-after-return)
return QLatin1String("s");
} else if (!direction.compare(QLatin1String("w"), Qt::CaseInsensitive) || !direction.compare(QLatin1String("west"), Qt::CaseInsensitive)) { // NOLINT(readability-else-after-return)
return QLatin1String("w");
} else if (!direction.compare(QLatin1String("u"), Qt::CaseInsensitive) || !direction.compare(QLatin1String("up"), Qt::CaseInsensitive)) { // NOLINT(readability-else-after-return)
return QLatin1String("up");
} else if (!direction.compare(QLatin1String("d"), Qt::CaseInsensitive) || !direction.compare(QLatin1String("down"), Qt::CaseInsensitive)) { // NOLINT(readability-else-after-return)
return QLatin1String("down");
} else if (!direction.compare(QLatin1String("ne"), Qt::CaseInsensitive) || !direction.compare(QLatin1String("northeast"), Qt::CaseInsensitive) // NOLINT(readability-else-after-return)
|| !direction.compare(QLatin1String("north-east"), Qt::CaseInsensitive)) {
return QLatin1String("ne");
} else if (!direction.compare(QLatin1String("se"), Qt::CaseInsensitive) || !direction.compare(QLatin1String("southeast"), Qt::CaseInsensitive) // NOLINT(readability-else-after-return)
|| !direction.compare(QLatin1String("south-east"), Qt::CaseInsensitive)) {
return QLatin1String("se");
} else if (!direction.compare(QLatin1String("sw"), Qt::CaseInsensitive) || !direction.compare(QLatin1String("southwest"), Qt::CaseInsensitive) // NOLINT(readability-else-after-return)
|| !direction.compare(QLatin1String("south-west"), Qt::CaseInsensitive)) {
return QLatin1String("sw");
} else if (!direction.compare(QLatin1String("nw"), Qt::CaseInsensitive) || !direction.compare(QLatin1String("northwest"), Qt::CaseInsensitive) // NOLINT(readability-else-after-return)
|| !direction.compare(QLatin1String("north-west"), Qt::CaseInsensitive)) {
return QLatin1String("nw");
} else if (!direction.compare(QLatin1String("i"), Qt::CaseInsensitive) || !direction.compare(QLatin1String("in"), Qt::CaseInsensitive)) { // NOLINT(readability-else-after-return)
return QLatin1String("in");
} else if (!direction.compare(QLatin1String("o"), Qt::CaseInsensitive) || !direction.compare(QLatin1String("out"), Qt::CaseInsensitive)) { // NOLINT(readability-else-after-return)
return QLatin1String("out");
}
return direction;
}
return QString();
}
// No documentation available in wiki - internal function
int TLuaInterpreter::dirToNumber(lua_State* L, int position)
{
QString dir;
int dirNum;
if (lua_type(L, position) == LUA_TSTRING) {
dir = lua_tostring(L, position);
dir = dir.toLower();
if (!dir.compare(QLatin1String("n")) || !dir.compare(QLatin1String("north"))) {
return DIR_NORTH;
}
if (!dir.compare(QLatin1String("e")) || !dir.compare(QLatin1String("east"))) {
return DIR_EAST;
}
if (!dir.compare(QLatin1String("s")) || !dir.compare(QLatin1String("south"))) {
return DIR_SOUTH;
}
if (!dir.compare(QLatin1String("w")) || !dir.compare(QLatin1String("west"))) {
return DIR_WEST;
}
if (!dir.compare(QLatin1String("u")) || !dir.compare(QLatin1String("up"))) {
return DIR_UP;
}
if (!dir.compare(QLatin1String("d")) || !dir.compare(QLatin1String("down"))) {
return DIR_DOWN;
}
if (!dir.compare(QLatin1String("ne")) || !dir.compare(QLatin1String("northeast"))) {
return DIR_NORTHEAST;
}
if (!dir.compare(QLatin1String("nw")) || !dir.compare(QLatin1String("northwest"))) {
return DIR_NORTHWEST;
}
if (!dir.compare(QLatin1String("se")) || !dir.compare(QLatin1String("southeast"))) {
return DIR_SOUTHEAST;
}
if (!dir.compare(QLatin1String("sw")) || !dir.compare(QLatin1String("southwest"))) {
return DIR_SOUTHWEST;
}
if (!dir.compare(QLatin1String("i")) || !dir.compare(QLatin1String("in"))) {
return DIR_IN;
}
if (!dir.compare(QLatin1String("o")) || !dir.compare(QLatin1String("out"))) {
return DIR_OUT;
}
}
if (lua_type(L, position) == LUA_TNUMBER) {
dirNum = static_cast<int>(lua_tonumber(L, position));
return (dirNum >= DIR_NORTH && dirNum <= DIR_OUT ? dirNum : 0);
}
return 0;
}
// Documentation: https://wiki.mudlet.org/w/Manual:Lua_Functions#denyCurrentSend
int TLuaInterpreter::denyCurrentSend(lua_State* L)
{
Host& host = getHostFromLua(L);
host.mAllowToSendCommand = false;
return 0;
}
// Documentation: https://wiki.mudlet.org/w/Manual:Lua_Functions#getProfileName
int TLuaInterpreter::getProfileName(lua_State* L)
{
Host& host = getHostFromLua(L);
lua_pushstring(L, host.getName().toUtf8().constData());
return 1;
}
// Documentation: https://wiki.mudlet.org/w/Manual:Lua_Functions#getCommandSeparator
int TLuaInterpreter::getCommandSeparator(lua_State* L)
{
Host& host = getHostFromLua(L);
lua_pushstring(L, host.getCommandSeparator().toUtf8().constData());
return 1;
}
// Documentation: https://wiki.mudlet.org/w/Manual:Lua_Functions#resetProfile
int TLuaInterpreter::resetProfile(lua_State* L)
{
Host& host = getHostFromLua(L);
const bool result = host.resetProfile_phase1();
lua_pushboolean(L, result);
return 1;
}
// Documentation: https://wiki.mudlet.org/w/Manual:Lua_Functions#getWindowsCodepage
int TLuaInterpreter::getWindowsCodepage(lua_State* L)
{
#if defined(Q_OS_WINDOWS)
QSettings registry(qsl(R"(HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Nls\CodePage)"), QSettings::NativeFormat);
auto value = registry.value(qsl("ACP"));
lua_pushstring(L, value.toString().toUtf8().constData());
return 1;
#else
return warnArgumentValue(L, __func__, "this function is only needed on Windows, and does not work here");
#endif
}
// Documentation: https://wiki.mudlet.org/w/Manual:Lua_Functions#spawn
int TLuaInterpreter::spawn(lua_State* L)
{
Host& host = getHostFromLua(L);
return TForkedProcess::startProcess(host.getLuaInterpreter(), L);
}
// Documentation: https://wiki.mudlet.org/w/Manual:Lua_Functions#loadReplay
int TLuaInterpreter::loadReplay(lua_State* L)
{
const QString replayFileName = getVerifiedString(L, __func__, 1, "replay file name");
if (replayFileName.isEmpty()) {
return warnArgumentValue(L, __func__, "a blank string is not a valid replay file name");
}
Host& host = getHostFromLua(L);
QString errMsg;
if (mudlet::self()->loadReplay(&host, replayFileName, &errMsg)) {
lua_pushboolean(L, true);
return 1;
}
// Although we only use English text for Lua messages the errMsg could
// contain a Windows pathFileName which may use non-ASCII characters:
return warnArgumentValue(L, __func__, qsl("unable to start replay, reason: '%1'").arg(errMsg));
}
// Documentation: https://wiki.mudlet.org/w/Manual:Lua_Functions#cut
int TLuaInterpreter::cut(lua_State* L)
{
const Host& host = getHostFromLua(L);
host.mpConsole->cut();
return 0;
}
// Internal helper for feedTelnet(...) and socketRaw(...) that enables the
// construction of data with bytes that cannot be prepared by normal means
// - including embedded nulls - for testing off-line and for writing protocol
// handlers for those that Mudlet does not provide itself respectively:
// Note: although "<<" and ">>" are extra codes that convert to '<' and '>'
// respectively this looks as though singular instances of either - or
// unrecognised "tags" with unknown other characters between them will still be
// passed through unchanged.
QByteArray TLuaInterpreter::parseTelnetCodes(const QByteArray& input)
{
// Increment the version - and document below - any changes to the table:
const int tableVersion = 1;
// Changes:
// 1 - Initial version.
/*
* Entry grouping
* * Hex digits
* * `O_` prefix - well known Telnet sub-option (less common/relevant ones ommitted)
* * ASCII character abbreviation
* * `T_` prefix - Telnet control code
*/
const QHash<QByteArray, unsigned char> lookupTable = {{QByteArray("<00>"), '\0'}, {QByteArray("<O_BINARY>"), '\0'}, {QByteArray("<NUL>"), '\0'},
{QByteArray("<01>"), '\x01'}, {QByteArray("<O_ECHO>"), '\x01'}, {QByteArray("<SOH>"), '\x01'},
{QByteArray("<02>"), '\x02'}, // Reconnect
{QByteArray("<STX>"), '\x02'},
{QByteArray("<03>"), '\x03'}, {QByteArray("<O_SGA>"), '\x03'}, {QByteArray("<ETX>"), '\x03'},
{QByteArray("<04>"), '\x04'}, // Approx Message Size Negotiation
{QByteArray("<EOT>"), '\x04'},
{QByteArray("<05>"), '\x05'}, {QByteArray("<O_STATUS>"), '\x05'}, {QByteArray("<ENQ>"), '\x05'},
{QByteArray("<06>"), '\x06'}, // Timing Mark
{QByteArray("<ACK>"), '\x06'},
{QByteArray("<07>"), '\x07'}, // Remote Controlled Trans and Echo
{QByteArray("<BELL>"), '\x07'},
{QByteArray("<08>"), '\x08'}, // Output Line Width
{QByteArray("<BS>"), '\x08'},
{QByteArray("<09>"), '\x09'}, // Output Page Size
{QByteArray("<HTAB>"), '\x09'},
{QByteArray("<0A>"), '\x0a'}, // Output Carriage-Return Disposition
{QByteArray("<LF>"), '\x0a'},
{QByteArray("<0B>"), '\x0b'}, // Output Horizontal Tab Stops
{QByteArray("<VTAB>"), '\x0b'},
{QByteArray("<0C>"), '\x0c'}, // Output Horizontal Tab Disposition
{QByteArray("<FF>"), '\x0c'},
{QByteArray("<0D>"), '\x0d'}, // Output Formfeed Disposition
{QByteArray("<CR>"), '\x0d'},
{QByteArray("<0E>"), '\x0e'}, // Output Vertical Tab Stops
{QByteArray("<SO>"), '\x0e'},
{QByteArray("<0F>"), '\x0f'}, // Output Vertical Tab Disposition
{QByteArray("<SI>"), '\x0f'},
{QByteArray("<10>"), '\x10'}, // Output Linefeed Disposition
{QByteArray("<DLE>"), '\x10'},
{QByteArray("<11>"), '\x11'}, // Extended ASCII
{QByteArray("<DC1>"), '\x11'},
{QByteArray("<12>"), '\x12'}, // Logout
{QByteArray("<DC2"), '\x12'},
{QByteArray("<13>"), '\x13'}, // Byte Macro
{QByteArray("<DC3>"), '\x13'},
{QByteArray("<14>"), '\x14'}, // Data Entry Terminal
{QByteArray("<DC4>"), '\x14'},
{QByteArray("<15>"), '\x15'}, // SUPDUP
{QByteArray("<NAK>"), '\x15'},
{QByteArray("<16>"), '\x16'}, // SUPDUP Output
{QByteArray("<SYN>"), '\x16'},
{QByteArray("<17>"), '\x17'}, // Send location
{QByteArray("<ETB>"), '\x17'},
{QByteArray("<18>"), '\x18'}, {QByteArray("<O_TERM>"), '\x18'}, {QByteArray("<CAN>"), '\x18'},
{QByteArray("<19>"), '\x19'}, {QByteArray("<O_EOR>"), '\x19'}, {QByteArray("<EM>"), '\x19'},
{QByteArray("<1A>"), '\x1a'}, // TACACS User Identification
{QByteArray("<SUB>"), '\x1a'},
{QByteArray("<1B>"), '\x1b'}, // Output Marking
{QByteArray("<ESC>"), '\x1b'},
{QByteArray("<1C>"), '\x1c'}, // Terminal Location Number
{QByteArray("<FS>"), '\x1c'},
{QByteArray("<1D>"), '\x1d'}, // Telnet 3270 Regime
{QByteArray("<GS>"), '\x1d'},
{QByteArray("<1E>"), '\x1e'}, // X.3 PAD
{QByteArray("<RS>"), '\x1e'},
{QByteArray("<1F>"), '\x1f'}, {QByteArray("<O_NAWS>"), '\x1f'}, {QByteArray("<US>"), '\x1f'},
{QByteArray("<SP>"), '\x20'}, // 32 dec, Space
{QByteArray("<O_NENV>"), '\x27'}, // 39 dec, New Environment (also MNES)
{QByteArray("<O_CHARS>"), '\x2a'}, // 42 dec, Character Set
{QByteArray("<O_KERMIT>"), '\x2f'}, // 47 dec
{QByteArray("<O_MSDP>"), '\x45'}, // 69 dec
{QByteArray("<O_MSSP>"), '\x46'}, // 70 dec
{QByteArray("<O_MCCP>"), '\x55'}, // 85 dec
{QByteArray("<O_MCCP2>"), '\x56'}, // 86 dec
{QByteArray("<O_MSP>"), '\x5a'}, // 90 dec
{QByteArray("<O_MXP>"), '\x5b'}, // 91 dec
{QByteArray("<O_ZENITH>"), '\x5d'}, // 93 dec
{QByteArray("<O_AARDWULF>"), '\x66'}, // 102 dec
{QByteArray("<DEL>"), '\x7f'}, // 127 dec
{QByteArray("<O_ATCP>"), '\xc8'}, // 200 dec
{QByteArray("<O_GMCP>"), '\xc9'}, // 201 dec
{QByteArray("<T_EOR>"), '\xef'}, // 239 dec
{QByteArray("<F0>"), '\xf0'}, {QByteArray("<T_SE>"), '\xf0'},
{QByteArray("<F1>"), '\xf1'}, {QByteArray("<T_NOP>"), '\xf1'},
{QByteArray("<F2>"), '\xf2'}, {QByteArray("<T_DM>"), '\xf2'},
{QByteArray("<F3>"), '\xf3'}, {QByteArray("<T_BRK>"), '\xf3'},
{QByteArray("<F4>"), '\xf4'}, {QByteArray("<T_IP>"), '\xf4'},
{QByteArray("<F5>"), '\xf5'}, {QByteArray("<T_ABOP>"), '\xf5'},
{QByteArray("<F6>"), '\xf6'}, {QByteArray("<T_AYT>"), '\xf6'},
{QByteArray("<F7>"), '\xf7'}, {QByteArray("<T_EC>"), '\xf7'},
{QByteArray("<F8>"), '\xf8'}, {QByteArray("<T_EL>"), '\xf8'},
{QByteArray("<F9>"), '\xf9'}, {QByteArray("<T_GA>"), '\xf9'},
{QByteArray("<FA>"), '\xfa'}, {QByteArray("<T_SB>"), '\xfa'},
{QByteArray("<FB>"), '\xfb'}, {QByteArray("<T_WILL>"), '\xfb'},
{QByteArray("<FC>"), '\xfc'}, {QByteArray("<T_WONT>"), '\xfc'},
{QByteArray("<FD>"), '\xfd'}, {QByteArray("<T_DO>"), '\xfd'},
{QByteArray("<FE>"), '\xfe'}, {QByteArray("<T_DONT>"), '\xfe'},
{QByteArray("<FF>"), '\xff'}, {QByteArray("<T_IAC>"), '\xff'}};
QByteArray bytes;
if (input.isEmpty()) {
bytes = QByteArray::number(tableVersion);
} else {
for (qsizetype index = 0, total = input.size(); index < total; ++index) {
if (input.at(index) == '<') {
// got an opening marker
if (((index + 1) < total) && (input.at(index + 1) == '<')) {
// got an escaped less than sign - so store it
bytes.append('<');
// nudge the index up one character
++index;
// and process the next one
continue;
}
// Else we haven't got an escaped one so find the closing greater then
qsizetype tagEnd = input.indexOf('>', index);
if (tagEnd > index) {
// Found it, so extract the whole tag including delimiters
QByteArray tag;
for (qsizetype i = index; i <= tagEnd; ++i) {
// store it
tag.append(input.at(i));
}
// look the tag up:
if (lookupTable.contains(tag)) {
bytes.append(lookupTable.value(tag));
} else {
// Not found so append the original tag instead
bytes.append(tag);
}
// nudge the index up to the closing greater then
index = tagEnd;
// and process the next character
continue;
}
} else {
if (input.at(index) == '>' && ((index + 1) < total) && (input.at(index + 1) == '>')) {
// got an escaped greater than sign - so store it
bytes.append('>');
// nudge the index up one character
++index;
// and process the next one
continue;
}
}
// Since we have not done anything that would have otherwise advanced
// index, then just copy the current character:
bytes.append(input.at(index));
}
}
return bytes;
}
// Documentation: https://wiki.mudlet.org/w/Manual:Lua_Functions#feedTelnet
int TLuaInterpreter::feedTelnet(lua_State* L)
{
Host& host = getHostFromLua(L);
if (!lua_isstring(L, 1)) {
lua_pushfstring(L,
"feedTelnet: bad argument #1 type (imitation game server data as string\n"
"expected, got %s!)",
luaL_typename(L, 1));
lua_error(L);
Q_UNREACHABLE();
}
if (host.mTelnet.getConnectionState() != QAbstractSocket::UnconnectedState) {
lua_pushnil(L);
lua_pushstring(L, "feedTelnet: refused, telnet connection socket is not in the unconnected state");
return 2;
}
// Same self-feeding-loop guard as feedTriggers(), but each nested telnet
// processing frame is large - see scmMaxLoopbackProcessingDepth.
if (host.mTelnet.loopbackProcessingDepth() >= cTelnet::scmMaxLoopbackProcessingDepth) {
qWarning().nospace() << "TLuaInterpreter::feedTelnet(...) aborting: nested telnet data processing reached the limit of " << cTelnet::scmMaxLoopbackProcessingDepth
<< " - probably an endless feedTelnet loop.";
const QString* pName = host.getTriggerUnit()->currentExecutingTriggerName();
if (pName && !pName->isEmpty()) {
lua_pushfstring(L,
"feedTelnet stopped to prevent a crash: trigger '%s' (or another trigger it feeds) is stuck in an endless loop - the data being fed keeps re-matching a trigger and "
"firing it again and again. Change the trigger's pattern or the fed data so they don't match each other.",
pName->toUtf8().constData());
} else {
lua_pushstring(L,
"feedTelnet stopped to prevent a crash: a trigger (or another trigger it feeds) is stuck in an endless loop - the data being fed keeps re-matching a trigger and firing "
"it again and again. Change the trigger's pattern or the fed data so they don't match each other.");
}
return lua_error(L);
}
const QByteArray rawData{lua_tostring(L, 1)};
// We need to convert any "<*>" codes to their raw byte forms:
QByteArray cookedData{parseTelnetCodes(rawData)};
if (rawData.isEmpty()) {
// This is a special case to get the table version
lua_pushboolean(L, true);
lua_pushfstring(L, "feedTelnet: using table version %s", cookedData.constData());
return 2;
}
host.mTelnet.loopbackTest(cookedData);
lua_pushboolean(L, true);
return 1;
}
// Documentation: https://wiki.mudlet.org/w/Manual:Lua_Functions#feedTriggers
int TLuaInterpreter::feedTriggers(lua_State* L)
{
Host& host = getHostFromLua(L);
if (!lua_isstring(L, 1)) {
lua_pushfstring(L,
"feedTriggers: bad argument #1 type (imitation game server text as string\n"
"expected, got %s!)",
luaL_typename(L, 1));
return lua_error(L);
}
// A self-feeding trigger recurses the C++ stack one level per re-match; abort
// with a catchable Lua error before that overflows the stack into a crash.
auto* triggerUnit = host.getTriggerUnit();
if (triggerUnit->processingDepth() >= TriggerUnit::scmMaxProcessingDepth) {
qWarning().nospace() << "TLuaInterpreter::feedTriggers(...) aborting: trigger processing recursion reached the limit of " << TriggerUnit::scmMaxProcessingDepth
<< " - probably an endless feedTriggers loop.";
const QString* pName = triggerUnit->currentExecutingTriggerName();
// The QByteArray temporary dies before lua_error()'s longjmp, which skips C++ destructors.
if (pName && !pName->isEmpty()) {
lua_pushfstring(L,
"feedTriggers stopped to prevent a crash: trigger '%s' (or another trigger it feeds) is stuck in an endless loop - the text being fed keeps re-matching a trigger and "
"firing it again and again. Change the trigger's pattern or the fed text so they don't match each other.",
pName->toUtf8().constData());
} else {
lua_pushstring(L,
"feedTriggers stopped to prevent a crash: a trigger (or another trigger it feeds) is stuck in an endless loop - the text being fed keeps re-matching a trigger and firing "
"it again and again. Change the trigger's pattern or the fed text so they don't match each other.");
}
return lua_error(L);
}
bool dataIsUtf8Encoded = true;
if (lua_gettop(L) > 1) {
dataIsUtf8Encoded = getVerifiedBool(L, __func__, 2, "Utf8Encoded", true);
}
const QByteArray data{lua_tostring(L, 1)};
const QByteArray currentEncoding = host.mTelnet.getEncoding();
if (dataIsUtf8Encoded) {
// We can convert the data from a QByteArray to a QString:
if (currentEncoding == "UTF-8") {
// Simple case: the encoding is already what we are using:
std::string dataStdString{data.toStdString()};
host.mpConsole->printOnDisplay(dataStdString);
lua_pushboolean(L, true);
return 1;
}
const QString dataQString{data};
// else
// We need to transcode it from UTF-8 into the current Game Server
// encoding - this can fail if it includes any characters (as UTF-8)
// that the game encoding cannot convey:
if (!(currentEncoding.isEmpty() || currentEncoding == "ASCII")) {
if (!TEncodingHelper::canEncode(dataQString, currentEncoding)) {
return warnArgumentValue(L,
__func__,
qsl("cannot send '%1' as it contains one or more characters that cannot be conveyed in the current game server encoding of '%2'")
.arg(data.constData(), currentEncoding.constData()));
}
std::string encodedText{TEncodingHelper::encode(dataQString, currentEncoding).toStdString()};
host.mpConsole->printOnDisplay(encodedText);
lua_pushboolean(L, true);
return 1;
}
// else plain, raw ASCII, we hope!
for (const QChar c : dataQString) {
if (c.row() || c.cell() > 127) {
return warnArgumentValue(
L, __func__, qsl("cannot send '%1' as it contains one or more characters that cannot be conveyed in the current game server encoding of 'ASCII'").arg(data.constData()));
}
}
// It is safe to use the data directly now as we have already proved it
// to be plain ASCII
std::string dataStdString{dataQString.toStdString()};
host.mpConsole->printOnDisplay(dataStdString);
lua_pushboolean(L, true);
return 1;
}
// else the user is assumed to have coded it themselves into the Game
// Server's current encoding - the backwards "compatible" form:
std::string dataStdString{data.toStdString()};
host.mpConsole->printOnDisplay(dataStdString);
lua_pushboolean(L, true);
return 1;
}
// No documentation available in wiki - internal helper
// to get ID of stopwatch from either a (numeric) ID argument or a (string) name
// - used to refactor the same code out of four separate stop-watch functions:
std::tuple<bool, int> TLuaInterpreter::getWatchId(lua_State* L, Host& h)
{
if (lua_type(L, 1) == LUA_TNUMBER) {
return {true, static_cast<int>(lua_tointeger(L, 1))};
}
const QString name{lua_tostring(L, 1)};
// Using an empty string will return the first unnamed stopwatch:
const int watchId = h.findStopWatchId(name);
if (!watchId) {
lua_pushnil(L);
if (name.isEmpty()) {
lua_pushstring(L, "no unnamed stopwatches found");
} else {
lua_pushfstring(L, "stopwatch with name '%s' not found", name.toUtf8().constData());
}
return {false, 0};
}
return {true, watchId};
}
// Documentation: none - internal helper for getStopWatchBrokenDownTime()/getStopWatches()
void TLuaInterpreter::generateElapsedTimeTable(lua_State* L, const QStringList& elapsedTimeSplitString, const bool includeDecimalSeconds, const qint64 elapsedTimeMilliSeconds)
{
constexpr int expectedElements = 6;
if (elapsedTimeSplitString.size() < expectedElements) {
qWarning() << "TLuaInterpreter::generateElapsedTimeTable() ERROR: expected" << expectedElements << "elements in time string but got" << elapsedTimeSplitString.size();
lua_newtable(L);
return;
}
lua_newtable(L);
lua_pushstring(L, "negative");
// Qt 5.7 seemed to not like comparing a QString with a QLatin1Char so
// use a QLatin1String instead even though it is only a single character:
lua_pushboolean(L, elapsedTimeSplitString.at(0) == QLatin1String("-"));
lua_settable(L, -3);
lua_pushstring(L, "days");
lua_pushinteger(L, elapsedTimeSplitString.at(1).toInt());
lua_settable(L, -3);
lua_pushstring(L, "hours");
lua_pushinteger(L, elapsedTimeSplitString.at(2).toInt());
lua_settable(L, -3);
lua_pushstring(L, "minutes");
lua_pushinteger(L, elapsedTimeSplitString.at(3).toInt());
lua_settable(L, -3);
lua_pushstring(L, "seconds");
lua_pushinteger(L, elapsedTimeSplitString.at(4).toInt());
lua_settable(L, -3);
lua_pushstring(L, "milliSeconds");
lua_pushinteger(L, elapsedTimeSplitString.at(5).toInt());
lua_settable(L, -3);
if (includeDecimalSeconds) {
lua_pushstring(L, "decimalSeconds");
lua_pushnumber(L, elapsedTimeMilliSeconds / 1000.0);
lua_settable(L, -3);
}
}
// Documentation: https://wiki.mudlet.org/w/Manual:Lua_Functions#holdingModifiers
int TLuaInterpreter::holdingModifiers(lua_State* L)
{
Qt::KeyboardModifiers keyModifiers;
keyModifiers = static_cast<Qt::KeyboardModifiers>(getVerifiedInt(L, __func__, 1, "key modifier", true));
Qt::KeyboardModifiers modifiersHeld = QGuiApplication::queryKeyboardModifiers();
lua_pushboolean(L, modifiersHeld == keyModifiers);
return 1;
}
// Documentation: https://wiki.mudlet.org/w/Manual:Lua_Functions#enableCommandLine
// This (and the next) function originally only worked on TConsole instances
// to show/hide a command line at the bottom (and the first would create the
// TCommandLine if needed) but they have been extended to also work on extra
// Documentation: https://wiki.mudlet.org/w/Manual:Lua_Functions#getModulePath
int TLuaInterpreter::getModulePath(lua_State* L)
{
const QString moduleName = getVerifiedString(L, __func__, 1, "module name");
const Host& host = getHostFromLua(L);
QMap<QString, QStringList> modules = host.mInstalledModules;
if (modules.contains(moduleName)) {
const QString modPath = modules[moduleName][0];
lua_pushstring(L, modPath.toUtf8().constData());
return 1;
}
return warnArgumentValue(L, __func__, "module doesn't exist");
}
// Documentation: https://wiki.mudlet.org/w/Manual:Lua_Functions#getModulePriority
int TLuaInterpreter::getModulePriority(lua_State* L)
{
const QString moduleName = getVerifiedString(L, __func__, 1, "module name");
Host& host = getHostFromLua(L);
// Installing a module does not seed mModulePriorities, so whether the module
// exists has to be asked of mInstalledModules - the same list
// setModulePriority() checks. A module nobody has set a priority on has the
// default of 0 that the module manager and the saved profile use (#9655).
if (!host.mInstalledModules.contains(moduleName)) {
return warnArgumentValue(L, __func__, "module doesn't exist");
}
lua_pushnumber(L, host.mModulePriorities.value(moduleName));
return 1;
}
// Documentation: https://wiki.mudlet.org/w/Manual:Lua_Functions#setModulePriority
int TLuaInterpreter::setModulePriority(lua_State* L)
{
if (!checkStringArg(L, __func__, 1, "module name")) {
return lua_error(L);
}
const int modulePriority = getVerifiedInt(L, __func__, 2, "module priority");
const QString moduleName{lua_tostring(L, 1)};
Host& host = getHostFromLua(L);
if (!host.mInstalledModules.contains(moduleName)) {
return warnArgumentValue(L, __func__, "module doesn't exist");
}
host.mModulePriorities[moduleName] = modulePriority;
return 0;
}
// Documentation: https://wiki.mudlet.org/w/Manual:Lua_Functions#closeMudlet
int TLuaInterpreter::closeMudlet(lua_State* L)
{
Q_UNUSED(L)
mudlet::self()->armForceClose();
return 0;
}
// Documentation: https://wiki.mudlet.org/w/Manual:Lua_Functions#saveProfile
int TLuaInterpreter::saveProfile(lua_State* L)
{
Host& host = getHostFromLua(L);
if (!lua_isnoneornil(L, 2) && !checkStringArg(L, __func__, 2, "file name", true)) {
return lua_error(L);
}
QString saveToDir;
if (lua_isstring(L, 1)) {
saveToDir = lua_tostring(L, 1);
}
QString saveAsFile;
if (!lua_isnoneornil(L, 2)) {
saveAsFile = lua_tostring(L, 2);
if (!saveAsFile.endsWith(".xml", Qt::CaseInsensitive)) {
saveAsFile = saveAsFile + ".xml";
}
}
auto [ok, filename, error] = (saveAsFile.isNull()) ? host.saveProfile(saveToDir) : host.saveProfileAs(saveToDir + "/" + saveAsFile);
if (ok) {
lua_pushboolean(L, true);
lua_pushstring(L, (filename.toUtf8().constData()));
return 2;
}
auto message = QString("Couldn't save '%1' to '%2' because: %3").arg(host.getName(), filename, error);
return warnArgumentValue(L, __func__, message);
}
// Documentation: https://wiki.mudlet.org/w/Manual:Lua_Functions#getMudletInfo
int TLuaInterpreter::getMudletInfo(lua_State* L)
{
Host& host = getHostFromLua(L);
QStringList knownEncodings{"ASCII"};
// cTelnet::getEncoding() returns a QByteArray NOT a QString:
QString currentEncoding{host.mTelnet.getEncoding()};
{
auto adjustEncoding = [](auto encodingName) {
auto originalEncoding = encodingName;
if (encodingName.startsWith(qsl("M_"))) {
encodingName.remove(0, 2);
}
return (originalEncoding == encodingName) ? originalEncoding : qsl("%1 (%2)").arg(encodingName).arg(originalEncoding);
};
// cTelnet::getEncodingsList() returns a QByteArrayList NOT a QStringList/QList<QString>:
for (const auto& encoding : host.mTelnet.getEncodingsList()) {
knownEncodings.append(adjustEncoding(QString(encoding)));
}
QCollator sorter;
sorter.setNumericMode(true);
sorter.setCaseSensitivity(Qt::CaseInsensitive);
std::sort(knownEncodings.begin(), knownEncodings.end(), sorter);
if (currentEncoding.isEmpty()) {
currentEncoding = qsl("\"ASCII\"");
} else {
currentEncoding = adjustEncoding(currentEncoding);
}
}
host.postMessage(qsl("[ INFO ] - Current encoding: %1").arg(currentEncoding));
host.postMessage(qsl("[ INFO ] - Available encodings:"));
host.postMessage(qsl(" %1").arg(knownEncodings.join(qsl(", "))));
return 0;
}
// Internal Function createLabel in an UserWindow
// The names arrive as the Lua-owned strings still anchored at stack indexes 1 and
// 2 rather than as QStrings: every check below can raise, and lua_error() longjmps
// past C++ destructors, so the QStrings are only built once nothing else can raise
int TLuaInterpreter::createLabelUserWindow(lua_State* L, const char* windowName, const char* labelName)
{
const int n = lua_gettop(L);
const int x = getVerifiedInt(L, "createLabel", 3, "label x-coordinate");
const int y = getVerifiedInt(L, "createLabel", 4, "label y-coordinate");
const int width = getVerifiedInt(L, "createLabel", 5, "label width");
const int height = getVerifiedInt(L, "createLabel", 6, "label height");
bool fillBackground = false;
if ((!lua_isnumber(L, 7)) && (!lua_isboolean(L, 7))) {
lua_pushfstring(L, "createLabel: bad argument #7 type (label fillBackground as boolean/number (0/1) expected, got %s!)", luaL_typename(L, 7));
return lua_error(L);
}
if (lua_isboolean(L, 7)) {
fillBackground = lua_toboolean(L, 7);
} else {
fillBackground = (lua_tointeger(L, 7) != 0);
}
bool clickthrough = false;
if (n >= 8) {
if ((!lua_isnumber(L, 8)) && (!lua_isboolean(L, 8))) {
lua_pushfstring(L, "createLabel: bad argument #8 type (label clickthrough as boolean/number (0/1) expected, got %s!)", luaL_typename(L, 8));
return lua_error(L);
}
if (lua_isboolean(L, 8)) {
clickthrough = lua_toboolean(L, 8);
} else {
clickthrough = (lua_tointeger(L, 8) != 0);
}
}
Host& host = getHostFromLua(L);
if (auto [success, message] = host.createLabel(QString{windowName}, QString{labelName}, x, y, width, height, fillBackground, clickthrough); !success) {
// We should, perhaps be returning a nil here but the published API
// says the function returns true or false and we cannot change that now
return warnArgumentValue(L, "createLabel", message, true);
}
lua_pushboolean(L, true);
return 1;
}
// Internal Function create Label in MainWindow
// See createLabelUserWindow() for why the name is not a QString
int TLuaInterpreter::createLabelMainWindow(lua_State* L, const char* labelName)
{
const int n = lua_gettop(L);
const int x = getVerifiedInt(L, "createLabel", 2, "label x-coordinate");
const int y = getVerifiedInt(L, "createLabel", 3, "label y-coordinate");
const int width = getVerifiedInt(L, "createLabel", 4, "label width");
const int height = getVerifiedInt(L, "createLabel", 5, "label height");
bool fillBackground = false;
if ((!lua_isnumber(L, 6)) && (!lua_isboolean(L, 6))) {
lua_pushfstring(L, "createLabel: bad argument #6 type (label fillBackground as boolean/number (0/1) expected, got %s!)", luaL_typename(L, 6));
return lua_error(L);
}
if (lua_isboolean(L, 6)) {
fillBackground = lua_toboolean(L, 6);
} else {
fillBackground = (lua_tointeger(L, 6) != 0);
}
bool clickthrough = false;
if (n >= 7) {
if ((!lua_isnumber(L, 7)) && (!lua_isboolean(L, 7))) {
lua_pushfstring(L, "createLabel: bad argument #7 type (label clickthrough as boolean/number (0/1) expected, got %s!)", luaL_typename(L, 7));
return lua_error(L);
}
if (lua_isboolean(L, 7)) {
clickthrough = lua_toboolean(L, 7);
} else {
clickthrough = (lua_tointeger(L, 7) != 0);
}
}
Host& host = getHostFromLua(L);
if (auto [success, message] = host.createLabel(qsl("main"), QString{labelName}, x, y, width, height, fillBackground, clickthrough); !success) {
// We should, perhaps be returning a nil here but the published API
// says the function returns true or false and we cannot change that now
return warnArgumentValue(L, "createLabel", message, true);
}
lua_pushboolean(L, true);
return 1;
}
// Documentation: ? - public function but should stay undocumented -- compare https://github.com/Mudlet/Mudlet/issues/1149
int TLuaInterpreter::closeUserWindow(lua_State* L)
{
const QString text = getVerifiedString(L, __func__, 1, "name");
Host& host = getHostFromLua(L);
host.closeWindow(text);
return 0;
}
// Documentation: https://wiki.mudlet.org/w/Manual:Lua_Functions#startLogging
int TLuaInterpreter::startLogging(lua_State* L)
{
const Host& host = getHostFromLua(L);
const bool logOn = getVerifiedBool(L, __func__, 1, "turn logging on/off");
QString savedLogFileName;
if (host.mpConsole->mLogToLogFile) {
savedLogFileName = host.mpConsole->mLogFileName;
// Don't assume we will be able to find the file name once recording has
// stopped.
}
if (host.mpConsole->mLogToLogFile != logOn) {
host.mpConsole->toggleLogging(false);
// Changes state of host.mpConsole->mLogToLogFile, but that can't be
// really be called a side-effect!
lua_pushboolean(L, true);
if (host.mpConsole->mLogToLogFile) {
host.mpConsole->logButton->setChecked(true);
// Sets the button as checked but clicked() & pressed() signals are NOT generated
lua_pushfstring(L, "Main console output has started to be logged to file: %s", host.mpConsole->mLogFileName.toUtf8().constData());
lua_pushstring(L, host.mpConsole->mLogFileName.toUtf8().constData());
lua_pushnumber(L, 1);
} else {
host.mpConsole->logButton->setChecked(false);
lua_pushfstring(L, "Main console output has stopped being logged to file: %s", savedLogFileName.toUtf8().constData());
lua_pushstring(L, host.mpConsole->mLogFileName.toUtf8().constData());
lua_pushnumber(L, 0);
}
} else {
lua_pushnil(L);
if (host.mpConsole->mLogToLogFile) {
lua_pushfstring(L, "Main console output is already being logged to file: %s", host.mpConsole->mLogFileName.toUtf8().constData());
lua_pushstring(L, host.mpConsole->mLogFileName.toUtf8().constData());
lua_pushnumber(L, -1);
} else {
lua_pushstring(L, "Main console output was already not being logged to a file.");
lua_pushnil(L);
lua_pushnumber(L, -2);
}
}
return 4;
}
int TLuaInterpreter::appendLog(lua_State* L)
{
const QString text = getVerifiedString(L, __func__, 1, "text to append to logfile", true);
const Host& host = getHostFromLua(L);
host.mpConsole->buffer.appendLog(text);
return 0;
}
// No documentation available in wiki - internal function
// funcName is not a QString because a QByteArray made from one would still be
// alive inside the raising checks below - see checkStringArg()
int TLuaInterpreter::setLabelCallback(lua_State* L, const char* funcName)
{
Host& host = getHostFromLua(L);
if (!checkStringArg(L, funcName, 1, "label name")) {
return lua_error(L);
}
// the empty-name refusal has to stay ahead of the argument #2 check, as it
// did before, or setLabelClickCallback("", <bad callback>) would raise
// instead of returning nil and a message
if (*lua_tostring(L, 1) == '\0') {
return warnArgumentValue(L, __func__, "label name cannot be an empty string");
}
if (!lua_isnil(L, 2) && !lua_isfunction(L, 2)) {
lua_pushfstring(L, "%s: bad argument #2 type (function or nil expected, got %s!)", funcName, luaL_typename(L, 2));
return lua_error(L);
}
const QLatin1StringView callbackName{funcName};
const QString labelName{lua_tostring(L, 1)};
lua_remove(L, 1);
int func = 0;
if (lua_isnil(L, 1)) {
lua_pop(L, 1);
} else {
func = luaL_ref(L, LUA_REGISTRYINDEX);
}
bool lua_result = false;
if (callbackName == qsl("setLabelClickCallback")) {
lua_result = host.setLabelClickCallback(labelName, func);
} else if (callbackName == qsl("setLabelDoubleClickCallback")) {
lua_result = host.setLabelDoubleClickCallback(labelName, func);
} else if (callbackName == qsl("setLabelReleaseCallback")) {
lua_result = host.setLabelReleaseCallback(labelName, func);
} else if (callbackName == qsl("setLabelMoveCallback")) {
lua_result = host.setLabelMoveCallback(labelName, func);
} else if (callbackName == qsl("setLabelWheelCallback")) {
lua_result = host.setLabelWheelCallback(labelName, func);
} else if (callbackName == qsl("setLabelOnEnter")) {
lua_result = host.setLabelOnEnter(labelName, func);
} else if (callbackName == qsl("setLabelOnLeave")) {
lua_result = host.setLabelOnLeave(labelName, func);
} else {
luaL_unref(L, LUA_REGISTRYINDEX, func);
return warnArgumentValue(L, __func__, qsl("'%1' is not a known function name - bug in Mudlet, please report it").arg(funcName));
}
if (!lua_result) {
luaL_unref(L, LUA_REGISTRYINDEX, func);
return warnArgumentValue(L, __func__, qsl("label name '%1' not found").arg(labelName));
}
lua_pushboolean(L, true);
return 1;
}
// No documentation available in wiki - internal function
// this was an internal only function used by the package system, but it was
// inactive and has been removed
int TLuaInterpreter::showUnzipProgress(lua_State* L)
{
return warnArgumentValue(L, __func__, "removed command, this function is now inactive and does nothing");
}
// Documentation: https://wiki.mudlet.org/w/Manual:Lua_Functions#getMudletHomeDir
int TLuaInterpreter::getMudletHomeDir(lua_State* L)
{
Host& host = getHostFromLua(L);
const QString nativeHomeDirectory = mudlet::getMudletPath(enums::profileHomePath, host.getName());
lua_pushstring(L, nativeHomeDirectory.toUtf8().constData());
return 1;
}
// No documentation available in wiki - internal function used by printError in DebugTools.lua
int TLuaInterpreter::errorc(lua_State* L)
{
Host& host = getHostFromLua(L);
const int n = lua_gettop(L);
if (!n) {
// Nothing to show
return 0;
}
QString luaErrorText;
QString luaFunctionInfo;
luaErrorText = qsl(" %1").arg(lua_tostring(L, 1));
if (n == 2) {
luaFunctionInfo = qsl("%1").arg(lua_tostring(L, 2));
} else {
luaFunctionInfo = qsl(" <no debug data available> ");
}
luaFunctionInfo.append(QChar::LineFeed);
luaErrorText.append(QChar::LineFeed);
if (host.mpEditorDialog) {
host.mpEditorDialog->mpErrorConsole->print(QLatin1String("[ERROR:] "), QColor(Qt::blue), QColor(Qt::black));
host.mpEditorDialog->mpErrorConsole->print(luaFunctionInfo, QColor(Qt::green), QColor(Qt::black));
host.mpEditorDialog->mpErrorConsole->print(qsl(" %1").arg(luaErrorText), QColor(Qt::red), QColor(Qt::black));
}
if (host.mEchoLuaErrors) {
if (!host.mpConsole->buffer.isEmpty() && !host.mpConsole->buffer.lineBuffer.at(host.mpConsole->buffer.lineBuffer.size() - 1).isEmpty()) {
host.postMessage(qsl("\n"));
}
host.mpConsole->print(qsl("[ LUA ] - "), QColor(80, 160, 255), QColor(Qt::black));
host.mpConsole->print(qsl("ERROR: "), QColor(Qt::blue), QColor(Qt::black));
host.mpConsole->print(qsl("%1").arg(luaFunctionInfo), QColor(Qt::green), QColor(Qt::black));
host.mpConsole->print(qsl(" %1").arg(luaErrorText), QColor(200, 50, 42), QColor(Qt::black));
}
return 0;
}
// Documentation: https://wiki.mudlet.org/w/Manual:Lua_Functions#debugc -- not #debug - compare GlobalLua
int TLuaInterpreter::debug(lua_State* L)
{
const Host& host = getHostFromLua(L);
const int n = lua_gettop(L);
if (!n) {
// Nothing to show
return 0;
}
QString luaDebugText;
if (n > 1) {
for (int i = 0; i < n; ++i) {
luaDebugText += qsl(" (%1) %2").arg(QString::number(i + 1), lua_tostring(L, i + 1));
}
} else {
// n == 1
luaDebugText = qsl(" %1").arg(lua_tostring(L, 1));
}
luaDebugText.append(QChar::LineFeed);
if (host.mpEditorDialog) {
host.mpEditorDialog->mpErrorConsole->print(QLatin1String("[DEBUG:]"), QColor(Qt::blue), QColor(Qt::black));
host.mpEditorDialog->mpErrorConsole->print(luaDebugText, QColor(Qt::green), QColor(Qt::black));
}
return 0;
}
// No documentation available in wiki - internal function
int TLuaInterpreter::showHandlerError(lua_State* L)
{
Host& host = getHostFromLua(L);
if (!checkStringArg(L, __func__, 1, "event name") || !checkStringArg(L, __func__, 2, "error message")) {
return lua_error(L);
}
host.mLuaInterpreter.logEventError(QString{lua_tostring(L, 1)}, QString{lua_tostring(L, 2)});
return 0;
}
// Internal helper function for two following functions:
// returns 0 and a pointer to the TAction with the ID or
// the first one found with the name as the index item on success.
// returns a non-zero number and a nullptr on failure (the first is the number
// of items on the stack for the caller to return to ITS caller).
// does NOT return (normally) if the index item on the stack is not a number or
// a string
std::pair<int, TAction*> TLuaInterpreter::getTActionFromIdOrName(lua_State* L, const int index, const char* func)
{
auto& host = getHostFromLua(L);
auto argType = lua_type(L, index);
TAction* pItem = nullptr;
if (argType == LUA_TNUMBER) {
const int id = qRound(lua_tonumber(L, index));
if (id < 0) {
return {warnArgumentValue(L, func, qsl("item ID (%1) invalid, it must be equal or greater than zero").arg(id).toUtf8().constData()), pItem};
}
pItem = host.getActionUnit()->getAction(id);
if (!pItem) {
return {warnArgumentValue(L, func, qsl("no button item with ID %1 found").arg(id).toUtf8().constData()), pItem};
}
if (!pItem->isPushDownButton()) {
pItem = nullptr;
return {warnArgumentValue(L, func, qsl("item ID with %1 is not a push-down button").arg(id).toUtf8().constData()), pItem};
}
}
if (argType == LUA_TSTRING) {
const QString name = lua_tostring(L, index);
if (name.isEmpty()) {
return {warnArgumentValue(L, func, "item name must not be an empty string"), pItem};
}
pItem = host.getActionUnit()->findAction(name);
if (!pItem) {
return {warnArgumentValue(L, func, qsl("no button item with name '%1' found").arg(name).toUtf8().constData()), pItem};
}
if (!pItem->isPushDownButton()) {
pItem = nullptr;
return {warnArgumentValue(L, func, qsl("item with name '%1' is not a push-down button").arg(name).toUtf8().constData()), pItem};
}
}
if (!pItem) {
// we'll get here if the (index) argument is NOT usable:
lua_pushfstring(L, "%s: bad argument #%d type (ID as number or name as string expected, got %s!)", func, index, luaL_typename(L, index));
lua_error(L); // Does not return!
Q_UNREACHABLE();
}
return {0, pItem};
}
// Documentation: https://wiki.mudlet.org/w/Manual:Lua_Functions#tempColorTrigger
// This is documented as using a simple color table - but the numbers do not
// Documentation: https://wiki.mudlet.org/w/Manual:Mudlet_Object_Functions#tempAnsiColorTrigger
// This is the replacement for tempColorTrigger() which uses the right numbers
// for ANSI colours in the range 0 to 255 or TTrigger::scmDefault for default
// colour or TTrigger::scmIgnored ignore; it is anticipated that additional
// special values less than zero may be added to detect other types of text (or
// for a 16M colour value where the components have to be given)
// Documentation: https://wiki.mudlet.org/w/Manual:Lua_Functions#findItems
int TLuaInterpreter::findItems(lua_State* L)
{
const int n = lua_gettop(L);
if (!checkStringArg(L, __func__, 1, "item name") || !checkStringArg(L, __func__, 2, "item type")) {
return lua_error(L);
}
bool exactMatch = true;
bool caseSensitive = true;
if (n > 2) {
exactMatch = getVerifiedBool(L, __func__, 3, "exact match", true);
}
if (n > 3) {
caseSensitive = getVerifiedBool(L, __func__, 4, "case sensitive", true);
}
const auto name = QString{lua_tostring(L, 1)};
// Although we only use 6 ASCII strings the user may not enter a purely
// ASCII value which we might have to report...
const QString type{lua_tostring(L, 2)};
Host& host = getHostFromLua(L);
auto generateList = [](const auto vector, auto l) {
lua_newtable(l);
int index = 0;
for (const auto& item : vector) {
lua_pushnumber(l, ++index);
lua_pushnumber(l, item);
lua_settable(l, -3);
}
};
if (!type.compare(QLatin1String("timer"), Qt::CaseInsensitive)) {
const auto itemList = host.getTimerUnit()->findItems(name, exactMatch, caseSensitive);
generateList(itemList, L);
return 1;
}
if (!type.compare(QLatin1String("trigger"), Qt::CaseInsensitive)) {
const auto itemList = host.getTriggerUnit()->findItems(name, exactMatch, caseSensitive);
generateList(itemList, L);
return 1;
}
if (!type.compare(QLatin1String("alias"), Qt::CaseInsensitive)) {
const auto itemList = host.getAliasUnit()->findItems(name, exactMatch, caseSensitive);
generateList(itemList, L);
return 1;
}
if (!type.compare(QLatin1String("keybind"), Qt::CaseInsensitive)) {
const auto itemList = host.getKeyUnit()->findItems(name, exactMatch, caseSensitive);
generateList(itemList, L);
return 1;
}
if (!type.compare(QLatin1String("button"), Qt::CaseInsensitive)) {
const auto itemList = host.getActionUnit()->findItems(name, exactMatch, caseSensitive);
generateList(itemList, L);
return 1;
}
if (!type.compare(QLatin1String("script"), Qt::CaseInsensitive)) {
const auto itemList = host.getScriptUnit()->findItems(name, exactMatch, caseSensitive);
generateList(itemList, L);
return 1;
}
return warnArgumentValue(L, __func__, qsl("invalid item type '%1' given, it should be one of: 'alias', 'button', 'script', 'keybind', 'timer' or 'trigger'").arg(type));
}
// Documentation: https://wiki.mudlet.org/w/Manual:Lua_Functions#isAncestorsActive
int TLuaInterpreter::isAncestorsActive(lua_State* L)
{
auto id = getVerifiedInt(L, __func__, 1, "item ID");
// Although we only use ASCII strings for the type the user may not enter a
// purely ASCII value which we might have to report...
QString type = getVerifiedString(L, __func__, 2, "item type");
if (id < 0) {
// Must be zero or more but doesn't seem to be:
return warnArgumentValue(L, __func__, qsl("item ID as %1 does not seem to be parseable as a positive integer").arg(lua_tostring(L, 1)));
}
Host& host = getHostFromLua(L);
// Remember, QString::compare(...) returns zero for a match:
QString typeCheck{QLatin1String("timer")};
if (!type.compare(typeCheck, Qt::CaseInsensitive)) {
auto pT = host.getTimerUnit()->getTimer(id);
if (!pT) {
return warnArgumentValue(L, __func__, qsl("%1 item ID %2 does not exist").arg(typeCheck, QString::number(id)));
}
// Offset timer have their active state recorded differently
lua_pushboolean(L, pT->isOffsetTimer() ? pT->shouldAncestorsBeActive() : pT->ancestorsActive());
return 1;
}
typeCheck = QLatin1String("trigger");
if (!type.compare(typeCheck, Qt::CaseInsensitive)) {
auto pT = host.getTriggerUnit()->getTrigger(id);
if (!pT) {
return warnArgumentValue(L, __func__, qsl("%1 item ID %2 does not exist").arg(typeCheck, QString::number(id)));
}
lua_pushboolean(L, pT->ancestorsActive());
return 1;
}
typeCheck = QLatin1String("alias");
if (!type.compare(typeCheck, Qt::CaseInsensitive)) {
auto pT = host.getAliasUnit()->getAlias(id);
if (!pT) {
return warnArgumentValue(L, __func__, qsl("%1 item ID %2 does not exist").arg(typeCheck, QString::number(id)));
}
lua_pushboolean(L, pT->ancestorsActive());
return 1;
}
typeCheck = QLatin1String("keybind");
if (!type.compare(typeCheck, Qt::CaseInsensitive)) {
auto pT = host.getKeyUnit()->getKey(id);
if (!pT) {
return warnArgumentValue(L, __func__, qsl("%1 item ID %2 does not exist").arg(typeCheck, QString::number(id)));
}
lua_pushboolean(L, pT->ancestorsActive());
return 1;
}
typeCheck = QLatin1String("button");
if (!type.compare(typeCheck, Qt::CaseInsensitive)) {
auto pT = host.getActionUnit()->getAction(id);
if (!pT) {
return warnArgumentValue(L, __func__, qsl("%1 item ID %2 does not exist").arg(typeCheck, QString::number(id)));
}
lua_pushboolean(L, pT->ancestorsActive());
return 1;
}
typeCheck = QLatin1String("script");
if (!type.compare(typeCheck, Qt::CaseInsensitive)) {
auto pT = host.getScriptUnit()->getScript(id);
if (!pT) {
return warnArgumentValue(L, __func__, qsl("%1 item ID %2 does not exist").arg(typeCheck, QString::number(id)));
}
lua_pushboolean(L, pT->ancestorsActive());
return 1;
}
return warnArgumentValue(L, __func__, qsl("invalid item type '%1' given, it should be one (case insensitive) of: 'alias', 'button', 'script', 'keybind', 'timer' or 'trigger'").arg(type));
}
// Documentation: https://wiki.mudlet.org/w/Manual:Lua_Functions#ancestors
// returned by the helper lambdas below in place of a Lua return count: the
// message is already on the stack and the caller is expected to raise it
static constexpr int csmErrorAlreadyPushed = -1;
int TLuaInterpreter::ancestors(lua_State* L)
{
// the type QStrings must be destroyed before the raise, so the internal
// error paths report back instead of raising - see checkStringArg()
const int results = [&L, functionName = __func__]() -> int {
auto id = getVerifiedInt(L, functionName, 1, "item ID");
// Although we only use ASCII strings for the type the user may not enter a
// purely ASCII value which we might have to report...
QString type = getVerifiedString(L, functionName, 2, "item type");
if (id < 0) {
// Must be zero or more but doesn't seem to be:
return warnArgumentValue(L, functionName, qsl("item ID as %1 does not seem to be parseable as a positive integer").arg(lua_tostring(L, 1)));
}
Host& host = getHostFromLua(L);
// Remember, QString::compare(...) returns zero for a match:
QString typeCheck{QLatin1String("timer")};
if (!type.compare(typeCheck, Qt::CaseInsensitive)) {
auto pT = host.getTimerUnit()->getTimer(id);
if (!pT) {
return warnArgumentValue(L, functionName, qsl("%1 item ID %2 does not exist").arg(typeCheck, QString::number(id)));
}
const auto ancestorsList = pT->getAncestorList();
lua_newtable(L);
int index = 0;
for (const auto pAncestor : ancestorsList) {
if (!pAncestor) {
// Uh oh! This is not expected, so clear that table off the
// stack so we can push an error message there:
lua_pop(L, 1);
lua_pushfstring(L, "%s: internal error, got a nullptr whilst looking for an ancestor of the %s with ID: %i", functionName, typeCheck.toLatin1().constData(), id);
return csmErrorAlreadyPushed;
}
lua_pushnumber(L, ++index);
lua_newtable(L);
{
// We are confining ourselves to a small set of details here
// enough to help to build a table of the items perhaps but
// something to provide more details about each of the diffent
// item types (once the user knows which IDs/names to use to
// get them) would probably be a good idea as well:
lua_pushstring(L, "id");
lua_pushnumber(L, pAncestor->getID());
lua_settable(L, -3);
lua_pushstring(L, "name");
lua_pushstring(L, pAncestor->getName().toUtf8().constData());
lua_settable(L, -3);
lua_pushstring(L, "node");
if (pAncestor->isFolder()) {
if (!pAncestor->mPackageName.isEmpty() && pAncestor->mPackageName == pAncestor->getName()) {
lua_pushstring(L, "package");
} else {
lua_pushstring(L, "group");
}
} else {
// offset timers have a parent node that is NOT a group!
lua_pushstring(L, "item");
}
lua_settable(L, -3);
lua_pushstring(L, "isActive");
// Offset timer have their active state recorded differently
lua_pushboolean(L, pAncestor->isOffsetTimer() ? pAncestor->shouldBeActive() : pAncestor->isActive());
lua_settable(L, -3);
}
lua_settable(L, -3);
}
return 1;
}
typeCheck = QLatin1String("trigger");
if (!type.compare(typeCheck, Qt::CaseInsensitive)) {
auto pT = host.getTriggerUnit()->getTrigger(id);
if (!pT) {
return warnArgumentValue(L, functionName, qsl("%1 item ID %2 does not exist").arg(typeCheck, QString::number(id)));
}
const auto ancestorsList = pT->getAncestorList();
lua_newtable(L);
int index = 0;
for (const auto pAncestor : ancestorsList) {
if (!pAncestor) {
// Uh oh! This is not expected, so clear that table off the
// stack so we can push an error message there:
lua_pop(L, 1);
lua_pushfstring(L, "%s: internal error, got a nullptr whilst looking for an ancestor of the %s with ID: %i", functionName, typeCheck.toLatin1().constData(), id);
return csmErrorAlreadyPushed;
}
lua_pushnumber(L, ++index);
lua_newtable(L);
{
lua_pushstring(L, "id");
lua_pushnumber(L, pAncestor->getID());
lua_settable(L, -3);
lua_pushstring(L, "name");
lua_pushstring(L, pAncestor->getName().toUtf8().constData());
lua_settable(L, -3);
lua_pushstring(L, "node");
if (pAncestor->isFolder()) {
if (!pAncestor->mPackageName.isEmpty() && pAncestor->mPackageName == pAncestor->getName()) {
lua_pushstring(L, "package");
} else {
lua_pushstring(L, "group");
}
} else {
lua_pushstring(L, "item");
}
lua_settable(L, -3);
lua_pushstring(L, "isActive");
lua_pushboolean(L, pAncestor->isActive());
lua_settable(L, -3);
}
lua_settable(L, -3);
}
return 1;
}
typeCheck = QLatin1String("alias");
if (!type.compare(typeCheck, Qt::CaseInsensitive)) {
auto pT = host.getAliasUnit()->getAlias(id);
if (!pT) {
return warnArgumentValue(L, functionName, qsl("%1 item ID %2 does not exist").arg(typeCheck, QString::number(id)));
}
const auto ancestorsList = pT->getAncestorList();
lua_newtable(L);
int index = 0;
for (const auto pAncestor : ancestorsList) {
if (!pAncestor) {
// Uh oh! This is not expected, so clear that table off the
// stack so we can push an error message there:
lua_pop(L, 1);
lua_pushfstring(L, "%s: internal error, got a nullptr whilst looking for an ancestor of the %s with ID: %i", functionName, typeCheck.toLatin1().constData(), id);
return csmErrorAlreadyPushed;
}
lua_pushnumber(L, ++index);
lua_newtable(L);
{
lua_pushstring(L, "id");
lua_pushnumber(L, pAncestor->getID());
lua_settable(L, -3);
lua_pushstring(L, "name");
lua_pushstring(L, pAncestor->getName().toUtf8().constData());
lua_settable(L, -3);
lua_pushstring(L, "node");
if (pAncestor->isFolder()) {
if (!pAncestor->mPackageName.isEmpty() && pAncestor->mPackageName == pAncestor->getName()) {
lua_pushstring(L, "package");
} else {
lua_pushstring(L, "group");
}
} else {
lua_pushstring(L, "item");
}
lua_settable(L, -3);
lua_pushstring(L, "isActive");
lua_pushboolean(L, pAncestor->isActive());
lua_settable(L, -3);
}
lua_settable(L, -3);
}
return 1;
}
typeCheck = QLatin1String("keybind");
if (!type.compare(typeCheck, Qt::CaseInsensitive)) {
auto pT = host.getKeyUnit()->getKey(id);
if (!pT) {
return warnArgumentValue(L, functionName, qsl("%1 item ID %2 does not exist").arg(typeCheck, QString::number(id)));
}
const auto ancestorsList = pT->getAncestorList();
lua_newtable(L);
int index = 0;
for (const auto pAncestor : ancestorsList) {
if (!pAncestor) {
// Uh oh! This is not expected, so clear that table off the
// stack so we can push an error message there:
lua_pop(L, 1);
lua_pushfstring(L, "%s: internal error, got a nullptr whilst looking for an ancestor of the %s with ID: %i", functionName, typeCheck.toLatin1().constData(), id);
return csmErrorAlreadyPushed;
}
lua_pushnumber(L, ++index);
lua_newtable(L);
{
lua_pushstring(L, "id");
lua_pushnumber(L, pAncestor->getID());
lua_settable(L, -3);
lua_pushstring(L, "name");
lua_pushstring(L, pAncestor->getName().toUtf8().constData());
lua_settable(L, -3);
lua_pushstring(L, "node");
if (pAncestor->isFolder()) {
if (!pAncestor->mPackageName.isEmpty() && pAncestor->mPackageName == pAncestor->getName()) {
lua_pushstring(L, "package");
} else {
lua_pushstring(L, "group");
}
} else {
lua_pushstring(L, "item");
}
lua_settable(L, -3);
lua_pushstring(L, "isActive");
lua_pushboolean(L, pAncestor->isActive());
lua_settable(L, -3);
}
lua_settable(L, -3);
}
return 1;
}
typeCheck = QLatin1String("button");
if (!type.compare(typeCheck, Qt::CaseInsensitive)) {
auto pT = host.getActionUnit()->getAction(id);
if (!pT) {
return warnArgumentValue(L, functionName, qsl("%1 item ID %2 does not exist").arg(typeCheck, QString::number(id)));
}
const auto ancestorsList = pT->getAncestorList();
lua_newtable(L);
int index = 0;
for (const auto pAncestor : ancestorsList) {
if (!pAncestor) {
// Uh oh! This is not expected, so clear that table off the
// stack so we can push an error message there:
lua_pop(L, 1);
lua_pushfstring(L, "%s: internal error, got a nullptr whilst looking for an ancestor of the %s with ID: %i", functionName, typeCheck.toLatin1().constData(), id);
return csmErrorAlreadyPushed;
}
lua_pushnumber(L, ++index);
lua_newtable(L);
{
lua_pushstring(L, "id");
lua_pushnumber(L, pAncestor->getID());
lua_settable(L, -3);
lua_pushstring(L, "name");
lua_pushstring(L, pAncestor->getName().toUtf8().constData());
lua_settable(L, -3);
lua_pushstring(L, "node");
if (pAncestor->isFolder()) {
if (!pAncestor->mPackageName.isEmpty() && pAncestor->mPackageName == pAncestor->getName()) {
lua_pushstring(L, "package");
} else {
lua_pushstring(L, "group");
}
} else {
lua_pushstring(L, "item");
}
lua_settable(L, -3);
lua_pushstring(L, "isActive");
lua_pushboolean(L, pAncestor->isActive());
lua_settable(L, -3);
}
lua_settable(L, -3);
}
return 1;
}
typeCheck = QLatin1String("script");
if (!type.compare(typeCheck, Qt::CaseInsensitive)) {
auto pT = host.getScriptUnit()->getScript(id);
if (!pT) {
return warnArgumentValue(L, functionName, qsl("%1 item ID %2 does not exist").arg(typeCheck, QString::number(id)));
}
const auto ancestorsList = pT->getAncestorList();
lua_newtable(L);
int index = 0;
for (const auto pAncestor : ancestorsList) {
if (!pAncestor) {
// Uh oh! This is not expected, so clear that table off the
// stack so we can push an error message there:
lua_pop(L, 1);
lua_pushfstring(L, "%s: internal error, got a nullptr whilst looking for an ancestor of the %s with ID: %i", functionName, typeCheck.toLatin1().constData(), id);
return csmErrorAlreadyPushed;
}
lua_pushnumber(L, ++index);
lua_newtable(L);
{
lua_pushstring(L, "id");
lua_pushnumber(L, pAncestor->getID());
lua_settable(L, -3);
lua_pushstring(L, "name");
lua_pushstring(L, pAncestor->getName().toUtf8().constData());
lua_settable(L, -3);
lua_pushstring(L, "node");
if (pAncestor->isFolder()) {
if (!pAncestor->mPackageName.isEmpty() && pAncestor->mPackageName == pAncestor->getName()) {
lua_pushstring(L, "package");
} else {
lua_pushstring(L, "group");
}
} else {
lua_pushstring(L, "item");
}
lua_settable(L, -3);
lua_pushstring(L, "isActive");
lua_pushboolean(L, pAncestor->isActive());
lua_settable(L, -3);
}
lua_settable(L, -3);
}
return 1;
}
return warnArgumentValue(L, functionName, qsl("invalid item type '%1' given, it should be one (case insensitive) of: 'alias', 'button', 'script', 'keybind', 'timer' or 'trigger'").arg(type));
}();
if (results == csmErrorAlreadyPushed) {
return lua_error(L);
}
return results;
}
// Documentation: https://wiki.mudlet.org/w/Manual:Lua_Functions#getTimestamp
int TLuaInterpreter::getTimestamp(lua_State* L)
{
const int n = lua_gettop(L);
int s = 1;
if (n > 1 && !checkStringArg(L, __func__, s++, "mini console, user window or buffer name {may be omitted for the \"main\" console}")) {
return lua_error(L);
}
const auto luaLine = getVerifiedInt(L, __func__, s, "line number");
QString name;
if (n > 1) {
name = lua_tostring(L, 1);
if (name == QLatin1String("main")) {
// clear it so it is treated as the main console below
name.clear();
}
}
if (luaLine < 1) {
return warnArgumentValue(L, __func__, qsl("line number %1 invalid, it should be greater than zero").arg(luaLine));
}
const Host& host = getHostFromLua(L);
if (name.isEmpty()) {
if (luaLine < host.mpConsole->buffer.timeBuffer.size()) {
lua_pushstring(L, host.mpConsole->buffer.timeBuffer.at(luaLine).toUtf8().constData());
return 1;
}
return warnArgumentValue(L, __func__, qsl("line number %1 invalid, it is beyond the last line of the buffer").arg(luaLine));
}
auto pC = host.mpConsole->mSubConsoleMap.value(name);
if (!pC) {
return warnArgumentValue(L, __func__, qsl("mini console, user window or buffer '%1' not found").arg(name));
}
if (luaLine < pC->buffer.timeBuffer.size()) {
lua_pushstring(L, pC->buffer.timeBuffer.at(luaLine).toUtf8().constData());
return 1;
}
return warnArgumentValue(L, __func__, qsl("line number %1 invalid, it is beyond the last line of the buffer").arg(luaLine));
}
// No documentation available - internal function
void TLuaInterpreter::pushMapLabelPropertiesToLua(lua_State* L, const TMapLabel& label)
{
lua_pushstring(L, "X");
lua_pushnumber(L, label.pos.x());
lua_settable(L, -3);
lua_pushstring(L, "Y");
lua_pushnumber(L, label.pos.y());
lua_settable(L, -3);
lua_pushstring(L, "Z");
lua_pushnumber(L, qRound(label.pos.z()));
lua_settable(L, -3);
lua_pushstring(L, "Height");
lua_pushnumber(L, label.size.height());
lua_settable(L, -3);
lua_pushstring(L, "Width");
lua_pushnumber(L, label.size.width());
lua_settable(L, -3);
lua_pushstring(L, "Text");
lua_pushstring(L, label.text.toUtf8().constData());
lua_settable(L, -3);
lua_pushstring(L, "Pixmap");
lua_pushstring(L, label.base64EncodePixmap().constData());
lua_settable(L, -3);
lua_pushstring(L, "OnTop");
lua_pushboolean(L, label.showOnTop);
lua_settable(L, -3);
lua_pushstring(L, "Scaling");
lua_pushboolean(L, !label.noScaling);
lua_settable(L, -3);
lua_pushstring(L, "Temporary");
lua_pushboolean(L, label.temporary);
lua_settable(L, -3);
lua_pushstring(L, "FgColor");
{
lua_newtable(L);
lua_pushstring(L, "r");
lua_pushinteger(L, label.fgColor.red());
lua_settable(L, -3);
lua_pushstring(L, "g");
lua_pushinteger(L, label.fgColor.green());
lua_settable(L, -3);
lua_pushstring(L, "b");
lua_pushinteger(L, label.fgColor.blue());
lua_settable(L, -3);
}
lua_settable(L, -3);
lua_pushstring(L, "BgColor");
{
lua_newtable(L);
lua_pushstring(L, "r");
lua_pushinteger(L, label.bgColor.red());
lua_settable(L, -3);
lua_pushstring(L, "g");
lua_pushinteger(L, label.bgColor.green());
lua_settable(L, -3);
lua_pushstring(L, "b");
lua_pushinteger(L, label.bgColor.blue());
lua_settable(L, -3);
}
lua_settable(L, -3);
}
// Documentation: https://wiki.mudlet.org/w/Manual:Lua_Functions#getSpecialExits
// This function was slightly borked - the version in place from 2011 to 2020
// did not handle the corner case of multiple special exits that go to the same
// room, it would only show one of them at random. Each special exit was listed
// in its own table (against the key of the exit roomID) and it is a key to a
// "1" or "0" depending on whether the exit is locked or not. This was not
// The next functions are internal helpers for use by
// (echo|insert|set)|(Link|Popup) functions
// The non-raising counterpart of the type test in parseCommandOrFunction() - see checkStringArg()
bool TLuaInterpreter::checkCommandOrFunctionArg(lua_State* L, const char* functionName, const int pos)
{
if (!(lua_isstring(L, pos) || lua_isfunction(L, pos))) {
lua_pushfstring(L, "%s: bad argument #%d type (command as string or function expected, got %s!)", functionName, pos, luaL_typename(L, pos));
return false;
}
return true;
}
// No documentation available in wiki - internal function
void TLuaInterpreter::parseCommandOrFunction(lua_State* lState, const char* functionName, int& index, QString& command, int& luaFunctionNumber)
{
if (!checkCommandOrFunctionArg(lState, functionName, index)) {
lua_error(lState);
Q_UNREACHABLE();
}
if (lua_isfunction(lState, index)) {
lua_pushvalue(lState, index);
luaFunctionNumber = luaL_ref(lState, LUA_REGISTRYINDEX);
return;
}
command = lua_tostring(lState, index);
}
// No documentation available in wiki - internal function
// The non-raising counterpart of parseCommandsOrFunctionsTable() - see
// checkStringArg(). Validating the whole table up front means the caller's
// QStringList is not yet populated when a bad item is reported, and no registry
// reference has been taken that a raise would strand
bool TLuaInterpreter::checkCommandsOrFunctionsTable(lua_State* L, const char* functionName, const int index)
{
if (!lua_istable(L, index)) {
lua_pushfstring(L, "%s: bad argument #%d type (%s as table expected, got %s!)", functionName, index, "commands/functions", luaL_typename(L, index));
return false;
}
lua_pushnil(L);
int subIndex = 0;
while (lua_next(L, index)) {
++subIndex;
if (!(lua_isstring(L, -1) || lua_isfunction(L, -1))) {
lua_pushfstring(L, "%s: bad item #%d in table argument #%d in type (command as string or function expected, got %s!)", functionName, subIndex, index, luaL_typename(L, -1));
return false;
}
lua_pop(L, 1);
}
return true;
}
// No documentation available in wiki - internal function
// The non-raising counterpart of parseHintsTable() - see checkStringArg()
bool TLuaInterpreter::checkHintsTable(lua_State* L, const char* functionName, const int index)
{
if (!lua_istable(L, index)) {
lua_pushfstring(L, "%s: bad argument #%d type (%s as table expected, got %s!)", functionName, index, "hints", luaL_typename(L, index));
return false;
}
lua_pushnil(L);
int subIndex = 0;
while (lua_next(L, index)) {
++subIndex;
if (!lua_isstring(L, -1)) {
lua_pushfstring(L, "%s: bad item #%d in table argument #%d in type (hint as string expected, got %s!)", functionName, subIndex, index, luaL_typename(L, -1));
return false;
}
lua_pop(L, 1);
}
return true;
}
// No documentation available in wiki - internal function
void TLuaInterpreter::parseHintsTable(lua_State* lState, const char* functionName, int& index, QStringList& hintList)
{
if (!lua_istable(lState, index)) {
// dead while every caller gates on checkHintsTable(), which duplicates
// this predicate: reaching it would strand the caller's hintList
lua_pushfstring(lState, "%s: bad argument #%d type (%s as table expected, got %s!)", functionName, index, "hints", luaL_typename(lState, index));
lua_error(lState);
Q_UNREACHABLE();
}
lua_pushnil(lState);
// Keep track of the index of the item in the table
int subIndex = 0;
while (lua_next(lState, index)) {
// key at index -2 and value at index -1
++subIndex;
if (!lua_isstring(lState, -1)) {
// dead while every caller gates on checkHintsTable() - see above
lua_pushfstring(lState, "%s: bad item #%d in table argument #%d in type (hint as string expected, got %s!)", functionName, subIndex, index, luaL_typename(lState, -1));
lua_error(lState);
Q_UNREACHABLE();
}
const QString hint = lua_tostring(lState, -1);
hintList << hint;
// removes value, but keeps key for next iteration
lua_pop(lState, 1);
}
}
// No documentation available in wiki - internal function
void TLuaInterpreter::parseCommandsOrFunctionsTable(lua_State* lState, const char* functionName, int& index, QStringList& commandsList, QVector<int>& luaFunctionNumbers)
{
if (!lua_istable(lState, index)) {
// dead while every caller gates on checkCommandsOrFunctionsTable(),
// which duplicates this predicate: reaching it would strand the
// caller's commandsList and every registry reference taken so far
lua_pushfstring(lState, "%s: bad argument #%d type (%s as table expected, got %s!)", functionName, index, "commands/functions", luaL_typename(lState, index));
lua_error(lState);
Q_UNREACHABLE();
}
lua_pushnil(lState);
// Keep track of the index of the item in the table
int subIndex = 0;
while (lua_next(lState, index)) {
// key at index -2 and value at index -1
++subIndex;
if (!(lua_isstring(lState, -1) || lua_isfunction(lState, -1))) {
// dead while every caller gates on checkCommandsOrFunctionsTable() - see above
lua_pushfstring(lState, "%s: bad item #%d in table argument #%d in type (command as string or function expected, got %s!)", functionName, subIndex, index, luaL_typename(lState, -1));
lua_error(lState);
Q_UNREACHABLE();
}
if (lua_isfunction(lState, -1)) {
lua_pushvalue(lState, -1);
luaFunctionNumbers << luaL_ref(lState, LUA_REGISTRYINDEX);
commandsList << QString();
} else {
const QString command = lua_tostring(lState, -1);
luaFunctionNumbers << 0;
commandsList << command;
}
// removes value, but keeps key for next iteration
lua_pop(lState, 1);
}
}
// No Documentation - public function but should stay undocumented -- compare https://github.com/Mudlet/Mudlet/issues/1149
int TLuaInterpreter::insertHTML(lua_State* L)
{
const QString sendText = getVerifiedString(L, __func__, 1, "sendText");
const Host& host = getHostFromLua(L);
host.mpConsole->insertHTML(sendText);
return 0;
}
// Documentation: https://wiki.mudlet.org/w/Manual:Lua_Functions#addSupportedTelnetOption
int TLuaInterpreter::addSupportedTelnetOption(lua_State* L)
{
const int option = getVerifiedInt(L, __func__, 1, "option");
Host& host = getHostFromLua(L);
host.mTelnet.supportedTelnetOptions[option] = true;
return 0;
}
// Documentation: https://wiki.mudlet.org/w/Manual:Lua_Functions#echo
int TLuaInterpreter::echo(lua_State* L)
{
Host& host = getHostFromLua(L);
const int n = lua_gettop(L);
int s = 1;
if (n > 1 && !checkStringArg(L, __func__, s++, "console name", true)) {
return lua_error(L);
}
if (!checkStringArg(L, __func__, s, "text to display")) {
return lua_error(L);
}
const QString consoleName = (n > 1) ? QString{lua_tostring(L, 1)} : QString();
const QString displayText{lua_tostring(L, s)};
if (isMain(consoleName)) {
host.mpConsole->buffer.mEchoingText = true;
host.mpConsole->echo(displayText);
host.mpConsole->buffer.mEchoingText = false;
// Writing to the main window must always succeed, but for consistent
// results, we now return a true for that
lua_pushboolean(L, true);
return 1;
}
if (!host.echoWindow(consoleName, displayText)) {
return warnArgumentValue(L, __func__, qsl("console/label '%1' does not exist").arg(consoleName));
}
lua_pushboolean(L, true);
return 1;
}
// Documentation: https://wiki.mudlet.org/w/Manual:Lua_Functions#setMergeTables
int TLuaInterpreter::setMergeTables(lua_State* L)
{
Host& host = getHostFromLua(L);
const int n = lua_gettop(L);
for (int i = 1; i <= n; ++i) {
if (!checkStringArg(L, __func__, i, "module")) {
return lua_error(L);
}
}
QStringList modulesList;
for (int i = 1; i <= n; ++i) {
modulesList << lua_tostring(L, i);
}
host.mGMCP_merge_table_keys = host.mGMCP_merge_table_keys + modulesList;
host.mGMCP_merge_table_keys.removeDuplicates();
return 0;
}
// Documentation: https://wiki.mudlet.org/w/Manual:Lua_Functions#getMudletVersion
int TLuaInterpreter::getMudletVersion(lua_State* L)
{
// the QByteArrays below must be destroyed before the raise, so failures
// report back instead of raising - see checkStringArg()
const int results = [&L, functionName = __func__]() -> int {
QByteArray version = QByteArray(APP_VERSION).trimmed();
const QByteArray build = mudlet::self()->mAppBuild.trimmed().toLocal8Bit();
QList<QByteArray> const versionData = version.split('.');
if (versionData.size() != 3) {
qWarning() << "TLuaInterpreter::getMudletVersion(): ERROR: Version data not correctly set on compilation,\n"
<< " is the VERSION value in the project file present?";
lua_pushstring(L, "getMudletVersion: sorry, version information not available.");
return csmErrorAlreadyPushed;
}
bool ok = true;
int major = 0;
int minor = 0;
int revision = 0;
{
major = versionData.at(0).toInt(&ok);
if (ok) {
minor = versionData.at(1).toInt(&ok);
}
if (ok) {
revision = versionData.at(2).toInt(&ok);
}
}
if (!ok) {
qWarning("TLuaInterpreter::getMudletVersion(): ERROR: Version data not correctly parsed,\n"
" was the VERSION value in the project file correct at compilation time?");
lua_pushstring(L, "getMudletVersion: sorry, version information corrupted.");
return csmErrorAlreadyPushed;
}
const int n = lua_gettop(L);
if (n == 1) {
if (!checkStringArg(L, functionName, 1, "style", true)) {
return csmErrorAlreadyPushed;
}
const QString tidiedWhat = QString{lua_tostring(L, 1)}.toLower().trimmed();
if (tidiedWhat.contains("major")) {
lua_pushinteger(L, major);
} else if (tidiedWhat.contains("minor")) { // NOLINT(readability-else-after-return)
lua_pushinteger(L, minor);
} else if (tidiedWhat.contains("revision")) { // NOLINT(readability-else-after-return)
lua_pushinteger(L, revision);
} else if (tidiedWhat.contains("build")) { // NOLINT(readability-else-after-return)
if (build.isEmpty()) {
lua_pushnil(L);
} else {
lua_pushstring(L, build);
}
} else if (tidiedWhat.contains("string")) { // NOLINT(readability-else-after-return)
if (build.isEmpty()) {
lua_pushstring(L, version.constData());
} else {
lua_pushstring(L, version.append(build).constData());
}
} else if (tidiedWhat.contains("table")) { // NOLINT(readability-else-after-return)
lua_pushinteger(L, major);
lua_pushinteger(L, minor);
lua_pushinteger(L, revision);
if (build.isEmpty()) {
lua_pushnil(L);
} else {
lua_pushstring(L, build);
}
return 4;
} else { // NOLINT(readability-else-after-return)
lua_pushstring(L,
"getMudletVersion: takes one (optional) argument:\n"
" \"major\", \"minor\", \"revision\", \"build\", \"string\" or \"table\".");
return csmErrorAlreadyPushed;
}
} else if (n == 0) { // NOLINT(readability-else-after-return)
lua_newtable(L);
lua_pushstring(L, "major");
lua_pushinteger(L, major);
lua_settable(L, -3);
lua_pushstring(L, "minor");
lua_pushinteger(L, minor);
lua_settable(L, -3);
lua_pushstring(L, "revision");
lua_pushinteger(L, revision);
lua_settable(L, -3);
lua_pushstring(L, "build");
lua_pushstring(L, mudlet::self()->mAppBuild.trimmed().toUtf8().constData());
lua_settable(L, -3);
} else { // NOLINT(readability-else-after-return)
lua_pushstring(L,
"getMudletVersion: only takes one (optional) argument:\n"
" \"major\", \"minor\", \"revision\", \"build\", \"string\" or \"table\".");
return csmErrorAlreadyPushed;
}
return 1;
}();
if (results == csmErrorAlreadyPushed) {
return lua_error(L);
}
return results;
}
// Documentation: https://wiki.mudlet.org/w/Manual:Lua_Functions#openWebPage
int TLuaInterpreter::openWebPage(lua_State* L)
{
const QString url = getVerifiedString(L, __func__, 1, "URL");
lua_pushboolean(L, mudlet::self()->openWebPage(url));
return 1;
}
// Documentation: https://wiki.mudlet.org/w/Manual:Lua_Functions#getTime
int TLuaInterpreter::getTime(lua_State* L)
{
const int n = lua_gettop(L);
bool return_string = false;
if (n > 0) {
return_string = getVerifiedBool(L, __func__, 1, "return as string", true);
if (n > 1 && !checkStringArg(L, __func__, 2, "custom time format")) {
return lua_error(L);
}
}
QString format = (n > 1) ? QString{lua_tostring(L, 2)} : qsl("yyyy.MM.dd hh:mm:ss.zzz");
QString tm;
const QDateTime time = QDateTime::currentDateTime();
if (return_string) {
tm = time.toString(format);
lua_pushstring(L, tm.toUtf8().constData());
} else {
const QDate dt = time.date();
const QTime tm = time.time();
lua_createtable(L, 0, 7);
lua_pushstring(L, "hour");
lua_pushinteger(L, tm.hour());
lua_rawset(L, n + 1);
lua_pushstring(L, "min");
lua_pushinteger(L, tm.minute());
lua_rawset(L, n + 1);
lua_pushstring(L, "sec");
lua_pushinteger(L, tm.second());
lua_rawset(L, n + 1);
lua_pushstring(L, "msec");
lua_pushinteger(L, tm.msec());
lua_rawset(L, n + 1);
lua_pushstring(L, "year");
lua_pushinteger(L, dt.year());
lua_rawset(L, n + 1);
lua_pushstring(L, "month");
lua_pushinteger(L, dt.month());
lua_rawset(L, n + 1);
lua_pushstring(L, "day");
lua_pushinteger(L, dt.day());
lua_rawset(L, n + 1);
}
return 1;
}
// Documentation: https://wiki.mudlet.org/w/Manual:Lua_Functions#getEpoch
int TLuaInterpreter::getEpoch(lua_State* L)
{
lua_pushnumber(L, static_cast<double>(QDateTime::currentDateTime().toMSecsSinceEpoch() / 1000.0));
return 1;
}
// Documentation: https://wiki.mudlet.org/w/Manual:Lua_Functions#selectCmdLineText
int TLuaInterpreter::addCmdLineBlacklist(lua_State* L)
{
const int n = lua_gettop(L);
// The mandatory text is last, but with no arguments at all that would be
// index 0 - not a valid Lua stack index, and Lua 5.1 hands back the first
// free slot for it rather than complaining:
const int textIndex = qMax(n, 1);
const char* name = "main";
if (n > 1) {
name = CMDLINE_NAME(L, 1);
}
if (!checkStringArg(L, __func__, textIndex, "suggestion text")) {
return lua_error(L);
}
auto pN = COMMANDLINE(L, QString{name});
pN->addBlacklist(QString{lua_tostring(L, textIndex)});
return 0;
}
// Documentation: https://wiki.mudlet.org/w/Manual:Lua_Functions#removeCmdLineBlacklist
int TLuaInterpreter::removeCmdLineBlacklist(lua_State* L)
{
const int n = lua_gettop(L);
// See addCmdLineBlacklist() on why the index is clamped:
const int textIndex = qMax(n, 1);
const char* name = "main";
if (n > 1) {
name = CMDLINE_NAME(L, 1);
}
if (!checkStringArg(L, __func__, textIndex, "suggestion text")) {
return lua_error(L);
}
auto pN = COMMANDLINE(L, QString{name});
pN->removeBlacklist(QString{lua_tostring(L, textIndex)});
return 0;
}
// Documentation: https://wiki.mudlet.org/w/Manual:Lua_Functions#clearCmdLineBlacklist
int TLuaInterpreter::clearCmdLineBlacklist(lua_State* L)
{
const int n = lua_gettop(L);
const char* name = "main";
if (n == 1) {
name = CMDLINE_NAME(L, 1);
}
auto pN = COMMANDLINE(L, QString{name});
pN->clearBlacklist();
return 0;
}
// Documentation: https://wiki.mudlet.org/w/Manual:Lua_Functions#installPackage
int TLuaInterpreter::installPackage(lua_State* L)
{
const QString location = getVerifiedString(L, __func__, 1, "package location path and file name");
Host& host = getHostFromLua(L);
if (auto [success, message] = host.installPackage(location, enums::PackageModuleType::Package, true); !success) {
return warnArgumentValue(L, __func__, message);
}
lua_pushboolean(L, true);
return 1;
}
// Documentation: https://wiki.mudlet.org/w/Manual:Lua_Functions#uninstallPackage
int TLuaInterpreter::uninstallPackage(lua_State* L)
{
const QString packageName = getVerifiedString(L, __func__, 1, "package name");
Host& host = getHostFromLua(L);
const bool result = host.uninstallPackage(packageName, enums::PackageModuleType::Package);
if (!result) {
lua_pushnil(L);
} else {
lua_pushboolean(L, result);
}
return 1;
}
// Documentation: https://wiki.mudlet.org/w/Manual:Lua_Functions#installModule
int TLuaInterpreter::installModule(lua_State* L)
{
const QString modName = getVerifiedString(L, __func__, 1, "module location");
Host& host = getHostFromLua(L);
const QString module = QDir::fromNativeSeparators(modName);
if (auto [success, message] = host.installPackage(module, enums::PackageModuleType::ModuleFromScript, true); !success) {
return warnArgumentValue(L, __func__, message);
}
auto moduleManager = host.mpModuleManager;
if (moduleManager && moduleManager->moduleTable->isVisible()) {
moduleManager->layoutModules();
}
lua_pushboolean(L, true);
return 1;
}
// Documentation: https://wiki.mudlet.org/w/Manual:Lua_Functions#uninstallModule
int TLuaInterpreter::uninstallModule(lua_State* L)
{
const QString module = getVerifiedString(L, __func__, 1, "module name");
Host& host = getHostFromLua(L);
if (!host.uninstallPackage(module, enums::PackageModuleType::ModuleFromScript)) {
lua_pushboolean(L, false);
return 1;
}
auto moduleManager = host.mpModuleManager;
if (moduleManager && moduleManager->moduleTable->isVisible()) {
moduleManager->layoutModules();
}
lua_pushboolean(L, true);
return 1;
}
// Documentation: https://wiki.mudlet.org/w/Manual:Lua_Functions#reloadModule
int TLuaInterpreter::reloadModule(lua_State* L)
{
const QString module = getVerifiedString(L, __func__, 1, "module name");
Host& host = getHostFromLua(L);
host.reloadModule(module);
return 0;
}
// Documentation: https://wiki.mudlet.org/w/Manual:Lua_Functions#enableModuleSync
int TLuaInterpreter::enableModuleSync(lua_State* L)
{
const QString module = getVerifiedString(L, __func__, 1, "module name");
Host& host = getHostFromLua(L);
if (auto [success, message] = host.changeModuleSync(module, QLatin1String("1")); !success) {
return warnArgumentValue(L, __func__, message);
}
auto moduleManager = host.mpModuleManager;
if (moduleManager && !moduleManager->moduleTable->findItems(module, Qt::MatchExactly).isEmpty()) {
const int row = moduleManager->moduleTable->findItems(module, Qt::MatchExactly)[0]->row();
auto checkItem = moduleManager->moduleTable->item(row, 2);
if (checkItem) {
checkItem->setCheckState(Qt::Checked);
}
}
lua_pushboolean(L, true);
return 1;
}
// Documentation: https://wiki.mudlet.org/w/Manual:Lua_Functions#disableModuleSync
int TLuaInterpreter::disableModuleSync(lua_State* L)
{
const QString module = getVerifiedString(L, __func__, 1, "module name");
Host& host = getHostFromLua(L);
if (auto [success, message] = host.changeModuleSync(module, QLatin1String("0")); !success) {
return warnArgumentValue(L, __func__, message);
}
auto moduleManager = host.mpModuleManager;
if (moduleManager && !moduleManager->moduleTable->findItems(module, Qt::MatchExactly).isEmpty()) {
const int row = moduleManager->moduleTable->findItems(module, Qt::MatchExactly)[0]->row();
auto checkItem = moduleManager->moduleTable->item(row, 2);
if (checkItem) {
checkItem->setCheckState(Qt::Unchecked);
}
}
lua_pushboolean(L, true);
return 1;
}
// Documentation: https://wiki.mudlet.org/w/Manual:Lua_Functions#getModuleSync
int TLuaInterpreter::getModuleSync(lua_State* L)
{
const QString module = getVerifiedString(L, __func__, 1, "module name");
Host& host = getHostFromLua(L);
auto [success, message] = host.getModuleSync(module);
if (!success) {
return warnArgumentValue(L, __func__, message);
}
lua_pushboolean(L, message == QLatin1String("1"));
return 1;
}
// Documentation: https://wiki.mudlet.org/w/Manual:Lua_Functions#getPackages
int TLuaInterpreter::getPackages(lua_State* L)
{
const Host& host = getHostFromLua(L);
auto packages = host.mInstalledPackages;
lua_newtable(L);
for (int i = 0; i < packages.size(); i++) {
lua_pushnumber(L, i + 1);
lua_pushstring(L, packages.at(i).toUtf8().constData());
lua_settable(L, -3);
}
return 1;
}
// Documentation: https://wiki.mudlet.org/w/Manual:Lua_Functions#getModules
int TLuaInterpreter::getModules(lua_State* L)
{
const Host& host = getHostFromLua(L);
auto modules = host.mInstalledModules;
int counter = 0;
QMap<QString, QStringList>::const_iterator iter = modules.constBegin();
lua_newtable(L);
while (iter != modules.constEnd()) {
lua_pushnumber(L, ++counter);
lua_pushstring(L, iter.key().toUtf8().constData());
lua_settable(L, -3);
++iter;
}
return 1;
}
// Documentation: https://wiki.mudlet.org/w/Manual:Lua_Functions#getModuleInfo
int TLuaInterpreter::getModuleInfo(lua_State* L)
{
const Host& host = getHostFromLua(L);
const int n = lua_gettop(L);
if (!checkStringArg(L, __func__, 1, "module name") || (n > 1 && !checkStringArg(L, __func__, 2, "info", true))) {
return lua_error(L);
}
auto infoMap = host.mModuleInfo;
const QString name{lua_tostring(L, 1)};
QString info;
if (n > 1) {
info = lua_tostring(L, 2);
}
if (info.isEmpty()) {
QMap<QString, QString>::const_iterator iter = infoMap.value(name).constBegin();
lua_newtable(L);
while (iter != infoMap.value(name).constEnd()) {
lua_pushstring(L, iter.key().toUtf8().constData());
lua_pushstring(L, iter.value().toUtf8().constData());
lua_settable(L, -3);
++iter;
}
} else {
lua_pushstring(L, infoMap.value(name).value(info).toUtf8().constData());
}
return 1;
}
// Documentation: https://wiki.mudlet.org/w/Manual:Lua_Functions#getPackageInfo
int TLuaInterpreter::getPackageInfo(lua_State* L)
{
const Host& host = getHostFromLua(L);
const int n = lua_gettop(L);
if (!checkStringArg(L, __func__, 1, "package name") || (n > 1 && !checkStringArg(L, __func__, 2, "info", true))) {
return lua_error(L);
}
auto infoMap = host.mPackageInfo;
const QString name{lua_tostring(L, 1)};
QString info;
if (n > 1) {
info = lua_tostring(L, 2);
}
if (info.isEmpty()) {
QMap<QString, QString>::const_iterator iter = infoMap.value(name).constBegin();
lua_newtable(L);
while (iter != infoMap.value(name).constEnd()) {
lua_pushstring(L, iter.key().toUtf8().constData());
lua_pushstring(L, iter.value().toUtf8().constData());
lua_settable(L, -3);
++iter;
}
} else {
lua_pushstring(L, infoMap.value(name).value(info).toUtf8().constData());
}
return 1;
}
// Documentation: https://wiki.mudlet.org/w/Manual:Lua_Functions#setModuleInfo
int TLuaInterpreter::setModuleInfo(lua_State* L)
{
Host& host = getHostFromLua(L);
if (!checkStringArg(L, __func__, 1, "module name") || !checkStringArg(L, __func__, 2, "info") || !checkStringArg(L, __func__, 3, "value")) {
return lua_error(L);
}
host.mModuleInfo[QString{lua_tostring(L, 1)}][QString{lua_tostring(L, 2)}] = QString{lua_tostring(L, 3)};
lua_pushboolean(L, true);
return 1;
}
// Documentation: https://wiki.mudlet.org/w/Manual:Lua_Functions#setPackageInfo
int TLuaInterpreter::setPackageInfo(lua_State* L)
{
Host& host = getHostFromLua(L);
if (!checkStringArg(L, __func__, 1, "package name") || !checkStringArg(L, __func__, 2, "info") || !checkStringArg(L, __func__, 3, "value")) {
return lua_error(L);
}
host.mPackageInfo[QString{lua_tostring(L, 1)}][QString{lua_tostring(L, 2)}] = QString{lua_tostring(L, 3)};
lua_pushboolean(L, true);
return 1;
}
// Documentation: https://wiki.mudlet.org/w/Manual:Lua_Functions#setDefaultAreaVisible
int TLuaInterpreter::setDefaultAreaVisible(lua_State* L)
{
const Host& host = getHostFromLua(L);
if (!host.mpMap || !host.mpMap->mpRoomDB) {
return warnArgumentValue(L, __func__, "no map present or loaded");
}
const bool isToShowDefaultArea = getVerifiedBool(L, __func__, 1, "isToShowDefaultArea");
if (host.mpMap->mpMapper) {
// If we are re-enabling the display of the default area
// AND the mapper was showing the default area
// the area widget will NOT be showing the correct area name afterwards
bool isAreaWidgetInNeedOfResetting = false;
if ((!host.mpMap->getDefaultAreaShown()) && (isToShowDefaultArea) && (host.mpMap->mpMapper->mp2dMap->mAreaID == -1)) {
isAreaWidgetInNeedOfResetting = true;
}
host.mpMap->setDefaultAreaShown(isToShowDefaultArea);
if (isAreaWidgetInNeedOfResetting) {
// Corner case fixup:
host.mpMap->mpMapper->comboBox_showArea->setCurrentText(host.mpMap->getDefaultAreaName());
}
host.mpMap->mpMapper->mp2dMap->repaint();
host.mpMap->mpMapper->update();
lua_pushboolean(L, true);
} else {
lua_pushboolean(L, false);
}
return 1;
}
// Documentation: https://wiki.mudlet.org/w/Manual:Lua_Functions#registerAnonymousEventHandler
// The function below is mostly unused now as it is overwritten in lua.
// The overwriting function poses as a transparent proxy and internally uses
// this function to get called events.
int TLuaInterpreter::registerAnonymousEventHandler(lua_State* L)
{
if (!checkStringArg(L, __func__, 1, "event name") || !checkStringArg(L, __func__, 2, "function name")) {
return lua_error(L);
}
Host& host = getHostFromLua(L);
host.registerAnonymousEventHandler(QString{lua_tostring(L, 1)}, QString{lua_tostring(L, 2)});
return 0;
}
// Documentation: https://wiki.mudlet.org/w/Manual:Lua_Functions#expandAlias
int TLuaInterpreter::expandAlias(lua_State* L)
{
if (!checkStringArg(L, __func__, 1, "text to parse")) {
return lua_error(L);
}
bool wantPrint = true;
if (lua_gettop(L) > 1) {
// check if the 2nd argument is a 'false', but don't match if it is 'nil'
// because expandAlias("command") should be the same as expandAlias("command", nil)
if (lua_isnil(L, 2)) {
wantPrint = false;
} else {
wantPrint = getVerifiedBool(L, __func__, 2, "echo", true);
}
}
const QString payload{lua_tostring(L, 1)};
Host& host = getHostFromLua(L);
// Host::send will encode the UTF encoded data here in the wanted Server
// encoding:
host.send(payload, wantPrint, false);
lua_pushboolean(L, true);
return 1;
}
// Documentation: https://wiki.mudlet.org/w/Manual:Lua_Functions#sendCmdLine
int TLuaInterpreter::sendCmdLine(lua_State* L)
{
const QString text = getVerifiedString(L, __func__, 1, "command");
Host& host = getHostFromLua(L);
host.sendCmdLine(text);
lua_pushboolean(L, true);
return 1;
}
// Documentation: https://wiki.mudlet.org/w/Manual:Lua_Functions#send
// Note this is registered as send NOT sendRaw - see initLuaGlobals()
// It converts the bytes in the command (the first argument) from Utf-8 to be
// encoded in the required Mud Server encoding.
int TLuaInterpreter::sendRaw(lua_State* L)
{
if (!checkStringArg(L, __func__, 1, "command")) {
return lua_error(L);
}
bool wantPrint = true;
if (lua_gettop(L) > 1) {
wantPrint = getVerifiedBool(L, __func__, 2, "showOnScreen", true);
}
const QString text{lua_tostring(L, 1)};
Host& host = getHostFromLua(L);
// Host::send will encode the UTF encoded data here in the wanted Server encoding:
host.send(text, wantPrint, true);
lua_pushboolean(L, true);
return 1;
}
// Documentation: https://wiki.mudlet.org/w/Manual:Lua_Functions#sendSocket
// The data can, theoretically, contain embedded ASCII NUL characters, but they
// cannot be entered directly as they immediately terminate the string. Instead
// provide a true as a second argument and use the appropriate "code" value
// defined in the parseTelnetCodes() function:
int TLuaInterpreter::sendSocket(lua_State* L)
{
if (!lua_isstring(L, 1)) {
lua_pushfstring(L, "sendSocket: bad argument #1 type (data as string expected, got %s!)", luaL_typename(L, 1));
return lua_error(L);
}
bool parseCodes = false;
if (lua_gettop(L) > 1) {
parseCodes = getVerifiedBool(L, __func__, 2, "parse telnet codes {default = false}", true);
}
const QByteArray data{lua_tostring(L, 1)};
std::string dataStdString{parseCodes ? parseTelnetCodes(data).toStdString() : data.toStdString()};
Host& host = getHostFromLua(L);
// msg is not in an encoded form here it is a literal set of bytes, which
// is what this usage needs:
if (!host.mTelnet.socketOutRaw(dataStdString)) {
lua_pushnil(L);
lua_pushstring(L, "sendSocket: unable to send any/all of the data, is the Server connected?");
return 2;
}
lua_pushboolean(L, true);
return 1;
}
// Documentation: https://wiki.mudlet.org/w/Manual:Lua_Functions#setServerEncoding
int TLuaInterpreter::setServerEncoding(lua_State* L)
{
Host& host = getHostFromLua(L);
if (!lua_isstring(L, 1)) {
lua_pushfstring(L, "setServerEncoding: bad argument #1 type (newEncoding as string expected, got %s!)", luaL_typename(L, 1));
return lua_error(L);
}
const QByteArray newEncoding = lua_tostring(L, 1);
QPair<bool, QString> const results = host.mTelnet.setEncoding(newEncoding);
if (!results.first) {
return warnArgumentValue(L, __func__, results.second);
}
lua_pushboolean(L, true);
return 1;
}
// Documentation: https://wiki.mudlet.org/w/Manual:Lua_Functions#getServerEncoding
int TLuaInterpreter::getServerEncoding(lua_State* L)
{
const Host& host = getHostFromLua(L);
// don't leak if we're using a Mudlet or a Qt-supplied codec to Lua
auto sanitizeEncoding = [](auto encodingName) {
if (encodingName.startsWith("M_")) {
encodingName.remove(0, 2);
}
return encodingName;
};
QByteArray encoding = host.mTelnet.getEncoding();
if (encoding.isEmpty()) {
encoding = "ASCII";
}
lua_pushstring(L, sanitizeEncoding(encoding).constData());
return 1;
}
// Documentation: https://wiki.mudlet.org/w/Manual:Lua_Functions#getServerEncodingsList
int TLuaInterpreter::getServerEncodingsList(lua_State* L)
{
const Host& host = getHostFromLua(L);
// don't leak if we're using a Mudlet or a Qt-supplied codec to Lua
auto sanitizeEncoding = [](auto encodingName) {
if (encodingName.startsWith("M_")) {
encodingName.remove(0, 2);
}
return encodingName;
};
lua_newtable(L);
lua_pushnumber(L, 1);
lua_pushstring(L, "ASCII");
lua_settable(L, -3);
for (int i = 0, total = host.mTelnet.getEncodingsList().count(); i < total; ++i) {
lua_pushnumber(L, i + 2); // Lua indexes start with 1 but we already have one entry
lua_pushstring(L, sanitizeEncoding(host.mTelnet.getEncodingsList().at(i)).constData());
lua_settable(L, -3);
}
return 1;
}
// Documentation: https://wiki.mudlet.org/w/Manual:Lua_Functions#getOS
int TLuaInterpreter::getOS(lua_State* L)
{
auto pushProcessor = [&]() {
#if defined(Q_PROCESSOR_IA64)
lua_pushstring(L, "ia64");
return 1;
#elif defined(Q_PROCESSOR_X86_64)
lua_pushstring(L, "x86 (64-bit)");
return 1;
#elif defined(Q_PROCESSOR_X86_32)
lua_pushstring(L, "x86 (32-bit)");
return 1;
#elif defined(Q_PROCESSOR_POWER_64)
lua_pushstring(L, "ppc (64-bit)");
return 1;
#elif defined(Q_PROCESSOR_POWER_32)
lua_pushstring(L, "ppc (32-bit)");
return 1;
#elif defined(Q_PROCESSOR_ARM_V7)
lua_pushstring(L, "arm7");
return 1;
#elif defined(Q_PROCESSOR_ARM_V6)
lua_pushstring(L, "arm6");
return 1;
#elif defined(Q_PROCESSOR_ARM_V5)
lua_pushstring(L, "arm5");
return 1;
#else
return 0;
#endif
};
#if defined(Q_OS_CYGWIN)
// Try for this one before Q_OS_WINDOWS as both are likely to be defined on
// a Cygwin platform
// CHECK: hopefully will NOT be triggered on mingw/msys
lua_pushstring(L, "cygwin");
lua_pushstring(L, QSysInfo::productVersion().toUtf8().constData());
return 2 + pushProcessor();
#elif defined(Q_OS_WINDOWS)
lua_pushstring(L, "windows");
lua_pushstring(L, QSysInfo::productVersion().toUtf8().constData());
return 2 + pushProcessor();
#elif defined(Q_OS_MACOS)
lua_pushstring(L, "mac");
lua_pushstring(L, QSysInfo::productVersion().toUtf8().constData());
return 2 + pushProcessor();
#elif defined(Q_OS_LINUX)
lua_pushstring(L, "linux");
lua_pushstring(L, QSysInfo::productVersion().toUtf8().constData());
lua_pushstring(L, QSysInfo::productType().toUtf8().constData());
return 3 + pushProcessor();
#elif defined(Q_OS_HURD)
lua_pushstring(L, "hurd");
lua_pushstring(L, QSysInfo::kernelVersion().toUtf8().constData());
return 2 + pushProcessor();
#elif defined(Q_OS_FREEBSD)
// Only defined on FreeBSD but NOT Debian kFreeBSD so we should check for
// this first
lua_pushstring(L, "freebsd");
lua_pushstring(L, QSysInfo::kernelVersion().toUtf8().constData());
return 2 + pushProcessor();
#elif defined(Q_OS_FREEBSD_KERNEL)
// Defined for BOTH Debian kFreeBSD hybrid with a GNU userland and
// main FreeBSD so it must be after Q_OS_FREEBSD check; included for Debian
// packager who may want to have this!
lua_pushstring(L, "kfreebsd");
lua_pushstring(L, QSysInfo::kernelVersion().toUtf8().constData());
return 2 + pushProcessor();
#elif defined(Q_OS_OPENBSD)
lua_pushstring(L, "openbsd");
lua_pushstring(L, QSysInfo::kernelVersion().toUtf8().constData());
return 2 + pushProcessor();
#elif defined(Q_OS_NETBSD)
lua_pushstring(L, "netbsd");
lua_pushstring(L, QSysInfo::kernelVersion().toUtf8().constData());
return 2 + pushProcessor();
#elif defined(Q_OS_BSD4)
// Generic *nix - must be before unix and after other more specific results
lua_pushstring(L, "bsd4");
lua_pushstring(L, QSysInfo::kernelVersion().toUtf8().constData());
return 2 + pushProcessor();
#elif defined(Q_OS_UNIX)
// Most generic *nix - must be after bsd4 and other more specific results
lua_pushstring(L, "unix");
lua_pushstring(L, QSysInfo::kernelVersion().toUtf8().constData());
return 2 + pushProcessor();
#else
lua_pushstring(L, "unknown");
lua_pushstring(L, "unknown");
return 2 + pushProcessor();
#endif
}
// Documentation: https://wiki.mudlet.org/w/Manual:Lua_Functions#getProcessID
int TLuaInterpreter::getProcessID(lua_State* L)
{
int pid = QCoreApplication::applicationPid();
lua_pushinteger(L, pid);
return 1;
}
// No documentation available in wiki - internal function
bool TLuaInterpreter::compileAndExecuteScript(const QString& code)
{
if (code.isEmpty()) {
return false;
}
lua_State* L = pGlobalLua;
const int callerStackTop = lua_gettop(L);
const int error = luaL_dostring(L, code.toUtf8().constData());
if (error) {
std::string e = "no error message available from Lua";
if (lua_isstring(L, -1)) {
e = "Lua error:";
e += lua_tostring(L, -1);
}
if (mudlet::smDebugMode) {
qDebug() << "LUA ERROR: code did not compile: ERROR:" << e.c_str();
}
const QString _n = "error in Lua code";
const QString _n2 = "no debug data available";
logError(e, _n, _n2);
}
lua_settop(L, callerStackTop);
return !error;
}
// No documentation available in wiki - internal function
// reformats given Lua code. In case of any issues, returns the original code as-is
// issues could be invalid Lua code or the formatter code bugging out
QString TLuaInterpreter::formatLuaCode(const QString& code)
{
if (code.isEmpty()) {
return code;
}
if (!pIndenterState) {
initIndenterGlobals();
}
lua_State* L = pIndenterState.get();
if (!validLuaCode(code).first) {
return code;
}
QString escapedCode = code;
// escape backslashes so we can pass \n to the function
escapedCode.replace(QLatin1String("\\"), QLatin1String("\\\\"));
// escape quotes since we'll be using quotes to pass data to the function
escapedCode.replace(QLatin1String("\""), QLatin1String("\\\""));
// escape newlines so they don't interpreted as newlines, but instead get passed onto the function
escapedCode.replace(QLatin1String("\n"), QLatin1String("\\n"));
const QString thing = QString(R"(return get_formatted_code(get_ast("%1"), {indent_chunk = ' ', right_margin = 100, max_text_width = 160, keep_comments = true}))").arg(escapedCode);
const int error = luaL_dostring(L, thing.toUtf8().constData());
if (error) {
std::string e = "no error message available from Lua";
if (lua_isstring(L, 1)) {
e = "Lua error:";
e += lua_tostring(L, 1);
}
if (mudlet::smDebugMode) {
qDebug() << "LUA ERROR: code did not compile: ERROR:" << e.c_str();
}
const QString objectName = "error in Lua code";
const QString functionName = "no debug data available";
logError(e, objectName, functionName);
lua_pop(L, lua_gettop(L));
return code;
}
QString result = lua_tostring(L, 1);
lua_pop(L, lua_gettop(L));
return result;
}
// No documentation available in wiki - internal function
bool TLuaInterpreter::compile(const QString& code, QString& errorMsg, const QString& name)
{
lua_State* L = pGlobalLua;
// This runs on the global lua_State, which is shared with whatever C
// function is calling us, so everything already on the stack is that
// caller's and has to be left exactly as it was found:
const int callerStackTop = lua_gettop(L);
const int error = (luaL_loadbuffer(L, code.toUtf8().constData(), strlen(code.toUtf8().constData()), name.toUtf8().constData()) || lua_pcall(L, 0, 0, 0));
if (error) {
// The error object is on the top of the stack. Absolute slot 1 - which
// this used to read - is the calling C function's first argument, which
// is how a failure came to be reported as the script's own name.
std::string e = "Lua syntax error:";
if (lua_isstring(L, -1)) {
e.append(lua_tostring(L, -1));
} else {
e.append("error object is a ").append(luaL_typename(L, -1)).append(" value");
}
errorMsg = qsl("<b>%1</b>").arg(QString::fromStdString(e).toHtmlEscaped());
if (mudlet::smDebugMode) {
auto& host = getHostFromLua(L);
TDebug(Qt::white, Qt::red) << "\n " << e.c_str() << "\n" >> &host;
}
} else if (mudlet::smDebugMode) {
auto& host = getHostFromLua(L);
TDebug(Qt::white, Qt::darkGreen) << "LUA: code compiled without errors. OK\n" >> &host;
}
lua_settop(L, callerStackTop);
return !error;
}
// No documentation available in wiki - internal function
// returns pair where first is bool stating true the given Lua code is valid, false otherwise
// second is empty if code is valid, error message if not valid
std::pair<bool, QString> TLuaInterpreter::validateLuaCodeParam(int index)
{
lua_State* L = pGlobalLua;
if (!lua_isstring(L, index)) {
return {false, qsl("lua script as string expected, got %1!").arg(luaL_typename(L, index))};
}
const QString script{lua_tostring(L, index)};
return validLuaCode(script);
}
// No documentation available in wiki - internal function
// Validates the Lua code argument at `index`; on success returns false. On
// failure it formats "<functionName>: bad argument #<index> (<reason>)" onto
// the Lua stack and returns true, expecting the caller to raise it with
// `return lua_error(L)` on the next line. Raising here would longjmp past this
// function's QString destructor and leak its buffer, so the message is copied
// into the Lua-owned stack string while the QString is alive and the QString is
// then released by this function's normal return, before the caller raises.
bool TLuaInterpreter::reportInvalidLuaCodeParam(lua_State* L, const char* functionName, const int index)
{
auto [isValid, errorMessage] = validateLuaCodeParam(index);
if (isValid) {
return false;
}
lua_pushfstring(L, "%s: bad argument #%d (%s)", functionName, index, errorMessage.toUtf8().constData());
return true;
}
// No documentation available in wiki - internal function
// returns pair where first is bool stating true the given Lua code is valid, false otherwise
// second is empty if code is valid, error message if not valid
std::pair<bool, QString> TLuaInterpreter::validLuaCode(const QString& code)
{
lua_State* L = pGlobalLua;
const int error = luaL_loadbuffer(L, code.toUtf8().constData(), strlen(code.toUtf8().constData()), code.toUtf8().data());
const int topElementIndex = lua_gettop(L);
QString e = "invalid Lua code: ";
if (error) {
if (lua_isstring(L, topElementIndex)) {
e += lua_tostring(L, topElementIndex);
} else {
e += "No error message available from Lua";
}
}
lua_pop(L, 1);
return {!error, e};
}
// No documentation available in wiki - internal function
void TLuaInterpreter::setMultiCaptureGroups(const std::list<std::list<std::string>>& captureList, const std::list<std::list<int>>& posList, QVector<QVector<QPair<QString, QString>>>& nameGroups)
{
mMultiCaptureGroupList = captureList;
mMultiCaptureGroupPosList = posList;
mMultiCaptureNameGroups = nameGroups;
/*
* std::list< std::list<string> >::const_iterator mit = mMultiCaptureGroupList.begin();
*
* int k=1;
* for ( ; mit!=mMultiCaptureGroupList.end(); mit++, k++) {
* cout << "regex#"<<k<<" got:"<<endl;
* std::list<string>::const_iterator it = (*mit).begin();
* for ( int i=1; it!=(*mit).end(); it++, i++ ) {
* cout << i<<"#"<<"<"<<*it<<">"<<endl;
* }
* cout << "-----------------------------"<<endl;
* }
*/
}
// No documentation available in wiki - internal function
void TLuaInterpreter::setCaptureGroups(const std::list<std::string>& captureList, const std::list<int>& posList)
{
mCaptureGroupList = captureList;
mCaptureGroupPosList = posList;
/*
* std::list<string>::iterator it2 = mCaptureGroupList.begin();
* std::list<int>::iterator it1 = mCaptureGroupPosList.begin();
* int i=0;
* for ( ; it1!=mCaptureGroupPosList.end(); it1++, it2++, i++) {
* cout << "group#"<<i<<" begin="<<*it1<<" len="<<(*it2).size()<<"word="<<*it2<<endl;
* }
*/
}
// No documentation available in wiki - internal function
void TLuaInterpreter::setCaptureNameGroups(const NameGroupMatches& nameGroups, const NamedMatchesRanges& namePositions)
{
mCapturedNameGroups = nameGroups;
mCapturedNameGroupsPosList = namePositions;
}
// No documentation available in wiki - internal function
void TLuaInterpreter::clearCaptureGroups()
{
mCaptureGroupList.clear();
mCaptureGroupPosList.clear();
mMultiCaptureGroupList.clear();
mMultiCaptureGroupPosList.clear();
mCapturedNameGroups.clear();
mCapturedNameGroupsPosList.clear();
mMultiCaptureNameGroups.clear();
lua_State* L = pGlobalLua;
const int callerStackTop = lua_gettop(L);
lua_newtable(L);
lua_setglobal(L, "matches");
lua_newtable(L);
lua_setglobal(L, "multimatches");
lua_settop(L, callerStackTop);
}
// No documentation available in wiki - internal function
void TLuaInterpreter::adjustCaptureGroups(int x, int a)
{
// adjust all capture group positions in line if data has been inserted by the user
for (int& it : mCaptureGroupPosList) {
if (it >= x) {
it += a;
}
}
for (auto& [pos, length] : mCapturedNameGroupsPosList) {
if (pos >= x) {
pos += a;
}
}
}
// No documentation available in wiki - internal function
void TLuaInterpreter::setAtcpTable(const QString& var, const QString& arg)
{
lua_State* L = pGlobalLua;
lua_getglobal(L, "atcp"); //defined in LuaGlobal.lua
lua_pushstring(L, var.toUtf8().constData());
lua_pushstring(L, arg.toUtf8().constData());
lua_rawset(L, -3);
lua_pop(L, 1);
TEvent event{};
event.mArgumentList.append(var);
event.mArgumentTypeList.append(ARGUMENT_TYPE_STRING);
event.mArgumentList.append(arg);
event.mArgumentTypeList.append(ARGUMENT_TYPE_STRING);
Host& host = getHostFromLua(L);
host.raiseEvent(event);
}
// No documentation available in wiki - internal function
void TLuaInterpreter::signalMXPEvent(const QString& type, const QMap<QString, QString>& attrs, const QStringList& actions, const QString& caption)
{
lua_State* L = pGlobalLua;
const int callerStackTop = lua_gettop(L);
lua_getglobal(L, "mxp");
if (!lua_istable(L, -1)) {
lua_pop(L, 1);
lua_newtable(L);
lua_setglobal(L, "mxp");
lua_getglobal(L, "mxp");
if (!lua_istable(L, -1)) {
qDebug() << "ERROR: mxp table not defined";
lua_settop(L, callerStackTop);
return;
}
}
lua_newtable(L);
lua_setfield(L, -2, type.toUtf8().toLower().constData());
lua_getfield(L, -1, type.toUtf8().toLower().constData());
if (!lua_istable(L, -1)) {
qDebug() << "ERROR: 'mxp." << type << "' table could not be defined";
lua_settop(L, callerStackTop);
return;
}
QMapIterator<QString, QString> itr(attrs);
while (itr.hasNext()) {
itr.next();
lua_pushstring(L, itr.value().toUtf8().constData());
lua_setfield(L, -2, itr.key().toUtf8().toLower().constData());
}
lua_newtable(L);
lua_setfield(L, -2, "actions");
lua_getfield(L, -1, "actions");
for (int i = 0; i < actions.size(); i++) {
lua_pushstring(L, actions[i].toUtf8().constData());
lua_rawseti(L, -2, i + 1);
}
lua_pop(L, 1);
lua_pushstring(L, caption.toUtf8().constData());
lua_setfield(L, -2, "text");
lua_settop(L, callerStackTop);
TEvent event{};
QString token("mxp");
token.append(".");
token.append(type.toUtf8().toLower().constData());
event.mArgumentList.append(token);
event.mArgumentTypeList.append(ARGUMENT_TYPE_STRING);
Host& host = getHostFromLua(L);
if (mudlet::smDebugMode) {
const QString msg = qsl("\n%1 event <%2> display(%1) to see the full content\n").arg("mxp", token);
host.mpConsole->printSystemMessage(msg);
}
host.raiseEvent(event);
}
// No documentation available in wiki - internal function
void TLuaInterpreter::setGMCPTable(QString& key, const QString& string_data)
{
lua_State* L = pGlobalLua;
lua_getglobal(L, "gmcp"); //defined in Lua init
if (!lua_istable(L, -1)) {
lua_pop(L, 1);
lua_newtable(L);
lua_setglobal(L, "gmcp");
lua_getglobal(L, "gmcp");
if (!lua_istable(L, -1)) {
qDebug() << "ERROR: gmcp table not defined";
lua_pop(L, 1);
return;
}
}
parseJSON(key, string_data, QLatin1String("gmcp"));
}
// No documentation available in wiki - internal function
void TLuaInterpreter::setMSSPTable(const QString& string_data)
{
lua_State* L = pGlobalLua;
lua_getglobal(L, "mssp"); //defined in Lua init
if (!lua_istable(L, -1)) {
lua_pop(L, 1);
lua_newtable(L);
lua_setglobal(L, "mssp");
lua_getglobal(L, "mssp");
if (!lua_istable(L, -1)) {
qDebug() << "ERROR: mssp table not defined";
lua_pop(L, 1);
return;
}
}
parseMSSP(string_data);
}
// No documentation available in wiki - internal function
void TLuaInterpreter::setMSDPTable(QString& key, const QString& string_data)
{
lua_State* L = pGlobalLua;
lua_getglobal(L, "msdp");
if (!lua_istable(L, -1)) {
lua_pop(L, 1);
lua_newtable(L);
lua_setglobal(L, "msdp");
lua_getglobal(L, "msdp");
if (!lua_istable(L, -1)) {
qDebug() << "ERROR: msdp table not defined";
lua_pop(L, 1);
return;
}
}
parseJSON(key, string_data, QLatin1String("msdp"));
}
// No documentation available in wiki - internal function
void TLuaInterpreter::parseJSON(QString& key, const QString& string_data, const QString& protocol)
{
// key is in format of Blah.Blah or Blah.Blah.Bleh - we want to push & pre-create the tables as appropriate
lua_State* L = pGlobalLua;
// Our callers push exactly the protocol's global table for us to fill in and
// we consume it, so everything below that is the stack of whatever C
// function this arrived in the middle of and must be left untouched. A
// negative level would make lua_settop() pop relatively instead:
const int callerStackTop = lua_gettop(L) - 1;
Q_ASSERT_X(callerStackTop >= 0, "TLuaInterpreter::parseJSON()", "the protocol's global table must already be on the stack");
QStringList tokenList = key.split(QLatin1Char('.'));
if (!lua_checkstack(L, tokenList.size() + 5)) {
qCritical() << "ERROR: could not grow Lua stack by" << tokenList.size() + 5 << "elements, parsing GMCP/MSDP failed. Current stack size is" << lua_gettop(L);
lua_settop(L, callerStackTop);
return;
}
int i = 0;
for (const int total = tokenList.size() - 1; i < total; ++i) {
lua_getfield(L, -1, tokenList.at(i).toUtf8().constData());
if (!lua_istable(L, -1)) {
lua_pop(L, 1);
lua_pushstring(L, tokenList.at(i).toUtf8().constData());
lua_newtable(L);
lua_rawset(L, -3);
lua_getfield(L, -1, tokenList.at(i).toUtf8().constData());
}
lua_remove(L, -2);
}
bool __needMerge = false;
lua_getfield(L, -1, tokenList.at(i).toUtf8().constData());
Host& host = getHostFromLua(L);
if (lua_istable(L, -1)) {
// only merge tables (instead of replacing them) if the key has been registered as a need to merge key by the user default is Char.Status only
if (host.mGMCP_merge_table_keys.contains(key)) {
__needMerge = true;
}
}
lua_pop(L, 1);
if (!__needMerge) {
lua_pushstring(L, tokenList.at(i).toUtf8().constData());
} else {
lua_pushstring(L, "__needMerge");
}
lua_getglobal(L, "json_to_value");
if (!lua_isfunction(L, -1)) {
lua_settop(L, callerStackTop);
qDebug() << "CRITICAL ERROR: json_to_value not defined";
return;
}
auto dataInUtf8 = string_data.toUtf8();
lua_pushlstring(L, dataInUtf8.constData(), dataInUtf8.length());
const int error = lua_pcall(L, 1, 1, 0);
if (!error) {
// Top of stack should now contain the lua representation of json.
lua_rawset(L, -3);
if (__needMerge) {
lua_settop(L, callerStackTop);
lua_getglobal(L, "__gmcp_merge_gmcp_sub_tables");
if (!lua_isfunction(L, -1)) {
lua_settop(L, callerStackTop);
qDebug() << "CRITICAL ERROR: __gmcp_merge_gmcp_sub_tables is not defined in lua_LuaGlobal.lua";
return;
}
lua_getglobal(L, "gmcp");
i = 0;
for (const int total = tokenList.size() - 1; i < total; ++i) {
lua_getfield(L, -1, tokenList.at(i).toUtf8().constData());
lua_remove(L, -2);
}
lua_pushstring(L, tokenList.at(i).toUtf8().constData());
if (lua_pcall(L, 2, 0, 0)) {
std::string e;
if (lua_isstring(L, -1)) {
e = "GMCP merge error: ";
e += lua_tostring(L, -1);
}
const QString _n = qsl("GMCP merge");
const QString _f = qsl("__gmcp_merge_gmcp_sub_tables");
logError(e, _n, _f);
}
}
} else {
{
std::string e;
if (lua_isstring(L, -1)) {
e = "Lua error:";
e += lua_tostring(L, -1);
}
const QString _n = "JSON decoder error:";
const QString _f = "json_to_value";
logError(e, _n, _f);
}
}
lua_settop(L, callerStackTop);
// events: for key "foo.bar.top" we raise: gmcp.foo, gmcp.foo.bar and gmcp.foo.bar.top
// with the actual key given as parameter e.g. event=gmcp.foo, param="gmcp.foo.bar"
QString token = protocol;
if (protocol == QLatin1String("msdp")) {
key.prepend(QLatin1String("msdp."));
} else {
key.prepend(QLatin1String("gmcp."));
}
for (int k = 0, total = tokenList.size(); k < total; ++k) {
TEvent event{};
token.append(".");
token.append(tokenList[k]);
event.mArgumentList.append(token);
event.mArgumentTypeList.append(ARGUMENT_TYPE_STRING);
event.mArgumentList.append(key);
event.mArgumentTypeList.append(ARGUMENT_TYPE_STRING);
if (mudlet::smDebugMode) {
const QString msg = qsl("\n%1 event <%2> display(%1) to see the full content\n").arg(protocol, token);
host.mpConsole->printSystemMessage(msg);
}
host.raiseEvent(event);
}
// auto-detect IRE composer
if (tokenList.size() == 3 && tokenList.at(0).toLower() == "ire" && tokenList.at(1).toLower() == "composer" && tokenList.at(2).toLower() == "edit") {
handleIreComposerEdit(string_data);
}
lua_settop(L, callerStackTop);
}
void TLuaInterpreter::handleIreComposerEdit(const QString& jsonData)
{
Host& host = getHostFromLua(pGlobalLua);
QJsonParseError parseError;
QJsonDocument jsonDoc = QJsonDocument::fromJson(jsonData.toUtf8(), &parseError);
if (parseError.error != QJsonParseError::NoError) {
qDebug() << "IRE Composer: JSON parse error:" << parseError.errorString();
return;
}
if (!jsonDoc.isObject()) {
qDebug() << "IRE Composer: JSON is not an object";
return;
}
QJsonObject jsonObj = jsonDoc.object();
if (!jsonObj.contains("title") || !jsonObj.contains("text")) {
qDebug() << "IRE Composer: Missing required 'title' or 'text' fields";
return;
}
const QString title = jsonObj["title"].toString();
const QString initialText = jsonObj["text"].toString();
if (host.mTelnet.mpComposer) {
return;
}
host.mTelnet.mpComposer = new dlgComposer(&host);
host.mTelnet.mpComposer->init(title, initialText);
host.mTelnet.mpComposer->raise();
host.mTelnet.mpComposer->show();
}
// No documentation available in wiki - internal function
void TLuaInterpreter::parseMSSP(const QString& string_data)
{
lua_State* L = pGlobalLua;
// setMSSPTable() pushes exactly the mssp global table for us and we consume
// it, so everything below that belongs to whichever C function this arrived
// in the middle of. A negative level would make lua_settop() pop relatively:
const int callerStackTop = lua_gettop(L) - 1;
Q_ASSERT_X(callerStackTop >= 0, "TLuaInterpreter::parseMSSP()", "the mssp global table must already be on the stack");
// string_data is in the format of MSSP_VAR "PLAYERS" MSSP_VAL "52" MSSP_VAR "UPTIME" MSSP_VAL "1234567890"
// The quote characters mean that the encased word is a string, the quotes themselves are not sent.
QStringList packageList = string_data.split(MSSP_VAR);
if (!packageList.isEmpty()) {
Host& host = getHostFromLua(L);
for (int i = 1; i < packageList.size(); i++) {
// clear the stack to avoid it getting to big
lua_settop(L, callerStackTop);
QStringList payloadList = packageList[i].split(MSSP_VAL);
if (payloadList.size() != 2) {
continue;
}
const QString msspVAR = payloadList[0];
const QString msspVAL = payloadList[1];
lua_getglobal(L, "mssp");
lua_pushstring(L, msspVAR.toUtf8().constData());
lua_pushlstring(L, msspVAL.toUtf8().constData(), msspVAL.toUtf8().length());
lua_rawset(L, -3);
// Raise an event
const QString protocol = qsl("mssp");
QString token = protocol;
token.append(".");
token.append(msspVAR);
TEvent event{};
event.mArgumentList.append(token);
event.mArgumentTypeList.append(ARGUMENT_TYPE_STRING);
event.mArgumentList.append(token);
event.mArgumentTypeList.append(ARGUMENT_TYPE_STRING);
if (mudlet::smDebugMode) {
const QString msg = qsl("\n%1 event <%2> display(%1) to see the full content\n").arg(protocol, token);
host.mpConsole->printSystemMessage(msg);
}
host.raiseEvent(event);
if (msspVAR == "HOSTNAME") {
host.mMSSPHostName = msspVAL;
} else if (msspVAR == "TLS" || msspVAR == "SSL") {
// In certain MSSP fields "-1" and "1" denote not supported/supported indicators,
// however the port is the standard value here. Ignore those values here.
host.mMSSPTlsPort = (msspVAL != "-1" && msspVAL != "1") ? msspVAL.toInt() : 0;
}
}
}
lua_settop(L, callerStackTop);
}
// No documentation available in wiki - internal function
// src is in Mud Server encoding and may need transcoding
// Includes MSDP code originally from recv_sb_msdp(...) in TinTin++'s telopt.c,
// https://tintin.sourceforge.io:
void TLuaInterpreter::msdp2Lua(const char* src)
{
Host& host = getHostFromLua(pGlobalLua);
const QByteArray transcodedSrc = host.mTelnet.decodeBytes(src);
QStringList varList;
QByteArray lastVar;
const int textLength = transcodedSrc.length();
int nest = 0;
quint8 last = 0;
QByteArray script;
bool no_array_marker_bug = false;
for (int i = 0; i < textLength; ++i) {
switch (transcodedSrc.at(i)) {
case MSDP_TABLE_OPEN:
script.append('{');
++nest;
last = MSDP_TABLE_OPEN;
break;
case MSDP_TABLE_CLOSE:
if (last == MSDP_VAL || last == MSDP_VAR) {
script.append('\"');
}
if (nest) {
--nest;
}
script.append('}');
last = MSDP_TABLE_CLOSE;
break;
case MSDP_ARRAY_OPEN:
script.append('[');
++nest;
last = MSDP_ARRAY_OPEN;
break;
case MSDP_ARRAY_CLOSE:
if (last == MSDP_VAL || last == MSDP_VAR) {
script.append('\"');
}
if (nest) {
--nest;
}
script.append(']');
last = MSDP_ARRAY_CLOSE;
break;
case MSDP_VAR:
if (nest) {
if (last == MSDP_VAL || last == MSDP_VAR) {
script.append('\"');
}
if (last == MSDP_VAL || last == MSDP_VAR || last == MSDP_TABLE_CLOSE || last == MSDP_ARRAY_CLOSE) {
script.append(',');
}
script.append('\"');
} else {
script.append('\"');
if (!varList.empty()) {
QString token = varList.front();
token = token.remove(QLatin1Char('\"'));
script = script.replace(0, varList.front().toUtf8().size() + 3, QByteArray());
mpHost->processDiscordMSDP(token, script);
setMSDPTable(token, script);
varList.clear();
script.clear();
}
}
last = MSDP_VAR;
lastVar.clear();
break;
case MSDP_VAL:
if (last == MSDP_VAR) {
script.append("\":");
}
if (last == MSDP_VAL) {
no_array_marker_bug = true;
script.append('\"');
}
if (last == MSDP_VAL || last == MSDP_TABLE_CLOSE || last == MSDP_ARRAY_CLOSE) {
script.append(',');
}
if (((textLength > i + 1) && transcodedSrc.at(i + 1) && transcodedSrc.at(i + 1) != MSDP_TABLE_OPEN && transcodedSrc.at(i + 1) != MSDP_ARRAY_OPEN) || (textLength <= i + 1)) {
script.append('\"');
}
varList.append(lastVar);
last = MSDP_VAL;
break;
case '\\':
script.append('\\');
break;
case '\"':
script.append('\\');
script.append('\"');
break;
default:
script.append(transcodedSrc.at(i));
lastVar.append(transcodedSrc.at(i));
break;
}
}
if (last != MSDP_ARRAY_CLOSE && last != MSDP_TABLE_CLOSE) {
script.append('\"');
if (!script.startsWith('\"')) {
script.prepend('\"');
}
}
if (!varList.empty()) {
QString token = varList.front();
token = token.remove(QLatin1Char('\"'));
script = script.replace(0, token.toUtf8().size() + 3, QByteArray());
if (no_array_marker_bug) {
if (!script.startsWith('[')) {
script.prepend('[');
script.append(']');
}
}
setMSDPTable(token, script);
}
}
// No documentation available in wiki - internal function
void TLuaInterpreter::setChannel102Table(int& var, int& arg)
{
lua_State* L = pGlobalLua;
lua_getglobal(L, "channel102"); //defined in LuaGlobal.lua
lua_pushnumber(L, var);
lua_pushnumber(L, arg);
lua_rawset(L, -3);
lua_pop(L, 1);
TEvent event{};
event.mArgumentList.append(QLatin1String("channel102Message"));
event.mArgumentTypeList.append(ARGUMENT_TYPE_STRING);
event.mArgumentList.append(QString::number(var));
event.mArgumentTypeList.append(ARGUMENT_TYPE_NUMBER);
event.mArgumentList.append(QString::number(arg));
event.mArgumentTypeList.append(ARGUMENT_TYPE_NUMBER);
Host& host = getHostFromLua(L);
host.raiseEvent(event);
}
// No documentation available in wiki - internal function
void TLuaInterpreter::setMatches(lua_State* L)
{
if (!mCaptureGroupList.empty()) {
lua_newtable(L);
// set values
int i = 1; // Lua indexes start with 1 as a general convention
for (auto it = mCaptureGroupList.begin(); it != mCaptureGroupList.end(); it++, i++) {
// if ((*it).length() < 1) continue; //have empty capture groups to be undefined keys i.e. matches[emptyCapGroupNumber] = nil otherwise it's = "" i.e. an empty string
lua_pushnumber(L, i);
lua_pushstring(L, (*it).c_str());
lua_settable(L, -3);
}
for (const auto& [name, capture] : mCapturedNameGroups) {
lua_pushstring(L, name.toUtf8().constData());
lua_pushstring(L, capture.toUtf8().constData());
lua_settable(L, -3);
}
lua_setglobal(L, "matches");
}
}
// No documentation available in wiki - internal function
bool TLuaInterpreter::call_luafunction(void* pT)
{
QElapsedTimer executionTimer;
if (mudlet::smDebugMode) {
executionTimer.start();
}
lua_State* L = pGlobalLua;
const int callerStackTop = lua_gettop(L);
lua_pushlightuserdata(L, pT);
lua_gettable(L, LUA_REGISTRYINDEX);
if (lua_isfunction(L, -1)) {
setMatches(L);
const int error = lua_pcall(L, 0, LUA_MULTRET, 0);
if (error) {
std::string e = "";
if (lua_isstring(L, -1)) {
e = "Lua error:";
e += lua_tostring(L, -1);
const QString _n = "error in anonymous Lua function";
const QString _n2 = "no debug data available";
logError(e, _n, _n2);
if (mudlet::smDebugMode) {
auto& host = getHostFromLua(L);
TDebug(Qt::white, Qt::red) << "LUA: ERROR running anonymous Lua function ERROR:" << e.c_str() >> &host;
}
}
} else {
if (mudlet::smDebugMode) {
auto& host = getHostFromLua(L);
TDebug(Qt::white, Qt::darkGreen) << "LUA OK anonymous Lua function ran without errors"
<< (executionTimer.isValid() ? " in " + QString::number(executionTimer.elapsed()) + "ms.\n" : ".\n")
>> &host;
}
}
lua_settop(L, callerStackTop);
return !error;
}
lua_settop(L, callerStackTop);
const QString _n = "error in anonymous Lua function";
const QString _n2 = "func reference not found by Lua, func cannot be called";
std::string e = "Lua error:";
logError(e, _n, _n2);
return false;
}
// No documentation available in wiki - internal function
void TLuaInterpreter::delete_luafunction(void* pT)
{
lua_State* L = pGlobalLua;
lua_pushlightuserdata(L, pT);
lua_pushnil(L);
lua_rawset(L, LUA_REGISTRYINDEX);
}
// No documentation available in wiki - internal function
void TLuaInterpreter::delete_luafunction(const QString& name)
{
lua_State* L = pGlobalLua;
const int callerStackTop = lua_gettop(L);
lua_getglobal(L, name.toUtf8().constData());
if (lua_isfunction(L, -1)) {
lua_pushnil(L);
lua_setglobal(L, name.toUtf8().constData());
} else if (mudlet::smDebugMode) {
qWarning() << "LUA: ERROR deleting " << name << ", it is not a function as expected";
}
lua_settop(L, callerStackTop);
}
// No documentation available in wiki - internal function
// returns true if function ran without errors
// as well as the boolean return value from the function
std::pair<bool, bool> TLuaInterpreter::callLuaFunctionReturnBool(void* pT)
{
QElapsedTimer executionTimer;
if (mudlet::smDebugMode) {
executionTimer.start();
}
lua_State* L = pGlobalLua;
const int callerStackTop = lua_gettop(L);
lua_pushlightuserdata(L, pT);
lua_gettable(L, LUA_REGISTRYINDEX);
bool returnValue = false;
if (lua_isfunction(L, -1)) {
setMatches(L);
const int error = lua_pcall(L, 0, LUA_MULTRET, 0);
if (error) {
std::string e = "";
if (lua_isstring(L, -1)) {
e = "Lua error:";
e += lua_tostring(L, -1);
const QString _n = "error in anonymous Lua function";
const QString _n2 = "no debug data available";
logError(e, _n, _n2);
if (mudlet::smDebugMode) {
auto& host = getHostFromLua(L);
TDebug(Qt::white, Qt::red) << "LUA: ERROR running anonymous Lua function ERROR:" << e.c_str() >> &host;
}
}
} else {
if (lua_gettop(L) > callerStackTop && lua_isboolean(L, -1)) {
returnValue = lua_toboolean(L, -1);
}
if (mudlet::smDebugMode) {
auto& host = getHostFromLua(L);
TDebug(Qt::white, Qt::darkGreen) << "LUA OK anonymous Lua function ran without errors"
<< (executionTimer.isValid() ? " in " + QString::number(executionTimer.elapsed()) + "ms.\n" : ".\n")
>> &host;
}
}
lua_settop(L, callerStackTop);
return {!error, returnValue};
}
lua_settop(L, callerStackTop);
const QString _n = "error in anonymous Lua function";
const QString _n2 = "func reference not found by Lua, func cannot be called";
std::string e = "Lua error:";
logError(e, _n, _n2);
return {false, false};
}
// No documentation available in wiki - internal function
// Third argument hides the "LUA OK" type message if it true which may be used
// to cut down on spammy output if things are okay.
bool TLuaInterpreter::call(const QString& function, const QString& mName, const bool muteDebugOutput)
{
QElapsedTimer executionTimer;
if (mudlet::smDebugMode) {
executionTimer.start();
}
lua_State* L = pGlobalLua;
const int callerStackTop = lua_gettop(L);
setMatches(L);
lua_getglobal(L, function.toUtf8().constData());
const int error = lua_pcall(L, 0, LUA_MULTRET, 0);
if (error) {
std::string e = "";
if (lua_isstring(L, -1)) {
e += lua_tostring(L, -1);
logError(e, mName, function);
if (mudlet::smDebugMode) {
auto& host = getHostFromLua(L);
TDebug(Qt::white, Qt::red) << "LUA ERROR: when running script " << mName << " (" << function << "),\nreason: " << e.c_str() << "\n" >> &host;
}
}
} else {
if (mudlet::smDebugMode && !muteDebugOutput) {
auto& host = getHostFromLua(L);
TDebug(Qt::white, Qt::darkGreen) << "LUA OK: script " << mName << " (" << function << ") ran without errors"
<< (executionTimer.isValid() ? " in " + QString::number(executionTimer.elapsed()) + "ms.\n" : ".\n")
>> &host;
}
}
lua_settop(L, callerStackTop);
return !error;
}
// No documentation available in wiki - internal function
std::pair<bool, bool> TLuaInterpreter::callReturnBool(const QString& function, const QString& mName)
{
QElapsedTimer executionTimer;
if (mudlet::smDebugMode) {
executionTimer.start();
}
lua_State* L = pGlobalLua;
const int callerStackTop = lua_gettop(L);
bool returnValue = false;
setMatches(L);
lua_getglobal(L, function.toUtf8().constData());
const int error = lua_pcall(L, 0, LUA_MULTRET, 0);
if (error) {
std::string e = "";
if (lua_isstring(L, -1)) {
e += lua_tostring(L, -1);
logError(e, mName, function);
if (mudlet::smDebugMode) {
auto& host = getHostFromLua(L);
TDebug(Qt::white, Qt::red) << "LUA: ERROR running script " << mName << " (" << function << ") ERROR:" << e.c_str() << "\n" >> &host;
}
}
} else {
if (lua_gettop(L) > callerStackTop && lua_isboolean(L, -1)) {
returnValue = lua_toboolean(L, -1);
}
if (mudlet::smDebugMode) {
auto& host = getHostFromLua(L);
TDebug(Qt::white, Qt::darkGreen) << "LUA OK script " << mName << " (" << function << ") ran without errors"
<< (executionTimer.isValid() ? " in " + QString::number(executionTimer.elapsed()) + "ms.\n" : ".\n")
>> &host;
}
}
lua_settop(L, callerStackTop);
return {!error, returnValue};
}
// No documentation available in wiki - internal function
void TLuaInterpreter::logError(std::string& e, const QString& name, const QString& function)
{
// Log error to Editor's Errors TConsole:
if (mpHost->mpEditorDialog) {
mpHost->mpEditorDialog->mpErrorConsole->print(qsl("[%1:]").arg(tr("ERROR")), QColor(Qt::blue), QColor(Qt::black));
mpHost->mpEditorDialog->mpErrorConsole->print(qsl(" %1:<%2> %3:<%4>\n")
.arg(
//: object is the Mudlet alias/trigger/script, used in this sample message: object:<Alias1> function:<cure_me>
tr("object"),
name,
//: function is the Lua function, used in this sample message: object:<Alias1> function:<cure_me>
tr("function"),
function),
QColor(Qt::green),
QColor(Qt::black));
mpHost->mpEditorDialog->mpErrorConsole->print(qsl(" <%1>\n").arg(e.c_str()), QColor(Qt::red), QColor(Qt::black));
}
// Log error to Profile's Main TConsole:
if (mpHost->mEchoLuaErrors) {
// ensure the Lua error is on a line of its own and is not prepended to
// the previous line, however there is a nasty gotcha in that during
// profile loading the (TMainConsole*) Host::mpConsole pointer is
// null - but then the buffer must itself be empty:
if (mpHost->mpConsole && !mpHost->mpConsole->buffer.isEmpty() && !mpHost->mpConsole->buffer.lineBuffer.at(mpHost->mpConsole->buffer.lineBuffer.size() - 1).isEmpty()) {
mpHost->postMessage(qsl("\n"));
}
mpHost->postMessage(qsl("[ LUA ] - %1: <%2> %3:<%4>\n<%5>")
.arg(
//: object is the Mudlet alias/trigger/script, used in this sample message: object:<Alias1> function:<cure_me>
tr("object"),
name,
//: function is the Lua function, used in this sample message: object:<Alias1> function:<cure_me>
tr("function"),
function,
e.c_str()));
}
}
// No documentation available in wiki - internal function
void TLuaInterpreter::logEventError(const QString& event, const QString& error)
{
// Log error to Editor's Errors TConsole:
if (mpHost->mpEditorDialog) {
mpHost->mpEditorDialog->mpErrorConsole->print(qsl("[%1:]").arg(tr("ERROR")), QColor(Qt::blue), QColor(Qt::black));
mpHost->mpEditorDialog->mpErrorConsole->print(qsl(" event handler for %1:\n").arg(event), QColor(Qt::green), QColor(Qt::black));
mpHost->mpEditorDialog->mpErrorConsole->print(qsl(" <%1>\n").arg(error), QColor(Qt::red), QColor(Qt::black));
}
// Log error to Profile's Main TConsole:
if (mpHost->mEchoLuaErrors) {
// ensure the Lua error is on a line of its own and is not prepended to the previous line
if (!mpHost->mpConsole->buffer.isEmpty() && !mpHost->mpConsole->buffer.lineBuffer.at(mpHost->mpConsole->buffer.lineBuffer.size() - 1).isEmpty()) {
mpHost->postMessage(qsl("\n"));
}
mpHost->postMessage(qsl("[ LUA ] - error in event handler for %1:\n<%2>").arg(event, error));
}
}
// No documentation available in wiki - internal function
bool TLuaInterpreter::callConditionFunction(std::string& function, const QString& mName)
{
QElapsedTimer executionTimer;
if (mudlet::smDebugMode) {
executionTimer.start();
}
lua_State* L = pGlobalLua;
const int callerStackTop = lua_gettop(L);
lua_getfield(L, LUA_GLOBALSINDEX, function.c_str());
const int error = lua_pcall(L, 0, 1, 0);
if (error) {
std::string e = "";
if (lua_isstring(L, -1)) {
e += lua_tostring(L, -1);
const QString _f = function.c_str();
logError(e, mName, _f);
if (mudlet::smDebugMode) {
auto& host = getHostFromLua(L);
TDebug(Qt::white, Qt::red) << "LUA: ERROR running script " << mName << " (" << function.c_str() << ") ERROR:" << e.c_str() << "\n" >> &host;
}
}
} else {
if (mudlet::smDebugMode) {
auto& host = getHostFromLua(L);
TDebug(Qt::white, Qt::darkGreen) << "LUA OK script " << mName << " (" << function.c_str() << ") ran without errors"
<< (executionTimer.isValid() ? " in " + QString::number(executionTimer.elapsed()) + "ms\n" : ".\n")
>> &host;
}
}
bool ret = false;
if (!error && lua_gettop(L) > callerStackTop) {
// Lua docs: Like all tests in Lua, lua_toboolean returns 1 for any Lua value different from false and nil; otherwise it returns 0
// This means trigger patterns don't have to strictly return true or false, as it is accepted in Lua
ret = lua_toboolean(L, -1);
}
lua_settop(L, callerStackTop);
return ((!error) && (ret > 0));
}
// No documentation available in wiki - internal function
bool TLuaInterpreter::callMulti(const QString& function, const QString& mName)
{
QElapsedTimer executionTimer;
if (mudlet::smDebugMode) {
executionTimer.start();
}
lua_State* L = pGlobalLua;
const int callerStackTop = lua_gettop(L);
if (!mMultiCaptureGroupList.empty()) {
int k = 1; // Lua indexes start with 1 as a general convention
lua_newtable(L); //multimatches
for (auto mit = mMultiCaptureGroupList.begin(); mit != mMultiCaptureGroupList.end(); mit++, k++) {
// multimatches{ trigger_idx{ table_matches{ ... } } }
lua_pushnumber(L, k);
lua_newtable(L); //regex-value => table matches
int i = 1; // Lua indexes start with 1 as a general convention
for (auto it = (*mit).begin(); it != (*mit).end(); it++, i++) {
lua_pushnumber(L, i);
lua_pushstring(L, (*it).c_str());
lua_settable(L, -3); //match in matches
}
for (const auto& [name, capture] : mMultiCaptureNameGroups.value(k - 1)) {
lua_pushstring(L, name.toUtf8().constData());
lua_pushstring(L, capture.toUtf8().constData());
lua_settable(L, -3);
}
lua_settable(L, -3); //matches in regex
}
lua_setglobal(L, "multimatches");
}
lua_getglobal(L, function.toUtf8().constData());
const int error = lua_pcall(L, 0, LUA_MULTRET, 0);
if (error) {
std::string e = "";
if (lua_isstring(L, -1)) {
e += lua_tostring(L, -1);
logError(e, mName, function);
if (mudlet::smDebugMode) {
auto& host = getHostFromLua(L);
TDebug(Qt::white, Qt::red) << "LUA: ERROR running script " << mName << " (" << function << ") ERROR:" << e.c_str() << "\n" >> &host;
}
}
} else {
if (mudlet::smDebugMode) {
auto& host = getHostFromLua(L);
TDebug(Qt::white, Qt::darkGreen) << "LUA OK script " << mName << " (" << function << ") ran without errors"
<< (executionTimer.isValid() ? " in " + QString::number(executionTimer.elapsed()) + "ms\n" : ".\n")
>> &host;
}
}
lua_settop(L, callerStackTop);
return !error;
}
// No documentation available in wiki - internal function
std::pair<bool, bool> TLuaInterpreter::callMultiReturnBool(const QString& function, const QString& mName)
{
QElapsedTimer executionTimer;
if (mudlet::smDebugMode) {
executionTimer.start();
}
lua_State* L = pGlobalLua;
const int callerStackTop = lua_gettop(L);
bool returnValue = false;
if (!mMultiCaptureGroupList.empty()) {
int k = 1; // Lua indexes start with 1 as a general convention
lua_newtable(L); //multimatches
for (auto mit = mMultiCaptureGroupList.begin(); mit != mMultiCaptureGroupList.end(); mit++, k++) {
// multimatches{ trigger_idx{ table_matches{ ... } } }
lua_pushnumber(L, k);
lua_newtable(L); //regex-value => table matches
int i = 1; // Lua indexes start with 1 as a general convention
for (auto it = (*mit).begin(); it != (*mit).end(); it++, i++) {
lua_pushnumber(L, i);
lua_pushstring(L, (*it).c_str());
lua_settable(L, -3); //match in matches
}
lua_settable(L, -3); //matches in regex
}
lua_setglobal(L, "multimatches");
}
lua_getglobal(L, function.toUtf8().constData());
const int error = lua_pcall(L, 0, LUA_MULTRET, 0);
if (error) {
std::string e = "";
if (lua_isstring(L, -1)) {
e += lua_tostring(L, -1);
logError(e, mName, function);
if (mudlet::smDebugMode) {
auto& host = getHostFromLua(L);
TDebug(Qt::white, Qt::red) << "LUA: ERROR running script " << mName << " (" << function << ") ERROR:" << e.c_str() << "\n" >> &host;
}
}
} else {
if (lua_gettop(L) > callerStackTop && lua_isboolean(L, -1)) {
returnValue = lua_toboolean(L, -1);
}
if (mudlet::smDebugMode) {
auto& host = getHostFromLua(L);
TDebug(Qt::white, Qt::darkGreen) << "LUA OK script " << mName << " (" << function << ") ran without errors"
<< (executionTimer.isValid() ? " in " + QString::number(executionTimer.elapsed()) + "ms\n" : ".\n")
>> &host;
}
}
lua_settop(L, callerStackTop);
return {!error, returnValue};
}
// No documentation available in wiki - internal function
bool TLuaInterpreter::callReference(lua_State* L, QString name, int parameters)
{
// Our callers have already pushed the function and its arguments for us, so
// anything below those belongs to whichever C function we are nested in.
// A negative level would make lua_settop() pop relatively instead:
const int callerStackTop = lua_gettop(L) - parameters - 1;
Q_ASSERT_X(callerStackTop >= 0, "TLuaInterpreter::callReference()", "the function to call and its arguments must already be on the stack");
int error = 0;
error = lua_pcall(L, parameters, LUA_MULTRET, 0);
if (error) {
std::string err = "";
if (lua_isstring(L, -1)) {
err += lua_tostring(L, -1);
}
logError(err, name, qsl("anonymous Lua function"));
if (mudlet::smDebugMode) {
auto& host = getHostFromLua(L);
TDebug(Qt::white, Qt::red) << "LUA: ERROR running anonymous Lua function (" << name << ")\nError: " << err.c_str() << "\n" >> &host;
}
}
lua_settop(L, callerStackTop);
return !error;
}
// No documentation available in wiki - internal function
bool TLuaInterpreter::callAnonymousFunction(const int func, QString name)
{
lua_State* L = pGlobalLua;
lua_rawgeti(L, LUA_REGISTRYINDEX, func);
return callReference(L, name, 0);
}
// No documentation available in wiki - internal function
bool TLuaInterpreter::callCmdLineAction(const int func, QString text)
{
lua_State* L = pGlobalLua;
auto& host = getHostFromLua(L);
// Suppress command line actions if remote echo is active (e.g., during password entry)
if (host.isRemoteEchoingActive()) {
return false; // Do not invoke actions during password entry
}
lua_rawgeti(L, LUA_REGISTRYINDEX, func);
lua_pushstring(L, text.toUtf8().constData());
return callReference(L, qsl("cmdLineAction"), 1);
}
// No documentation available in wiki - internal function
bool TLuaInterpreter::callLabelCallbackEvent(const int func, const QEvent* qE)
{
lua_State* L = pGlobalLua;
const int callerStackTop = lua_gettop(L);
lua_rawgeti(L, LUA_REGISTRYINDEX, func);
const QString name = qsl("label callback event");
if (qE) {
// Create Lua table with QEvent data if needed
switch (qE->type()) {
// This means the default argument value was used, so ignore
case (QEvent::None):
return callReference(L, name, 0);
// These are all QMouseEvents
case (QEvent::MouseButtonPress):
[[fallthrough]];
case (QEvent::MouseButtonDblClick):
[[fallthrough]];
case (QEvent::MouseButtonRelease):
[[fallthrough]];
case (QEvent::MouseMove): {
auto qME = static_cast<const QMouseEvent*>(qE);
lua_newtable(L);
// push button()
lua_pushstring(L, csmMouseButtons.value(qME->button()).toUtf8().constData());
lua_setfield(L, -2, qsl("button").toUtf8().constData());
// push buttons()
lua_newtable(L);
QMap<Qt::MouseButton, QString>::const_iterator iter = csmMouseButtons.constBegin();
int counter = 1;
while (iter != csmMouseButtons.constEnd()) {
if (iter.key() & qME->buttons()) {
lua_pushnumber(L, counter);
lua_pushstring(L, iter.value().toUtf8().constData());
lua_settable(L, -3);
counter++;
}
++iter;
}
lua_setfield(L, -2, qsl("buttons").toUtf8().constData());
auto globalPosition = qME->globalPosition().toPoint();
auto position = qME->position().toPoint();
// Push globalX()
lua_pushnumber(L, globalPosition.x());
lua_setfield(L, -2, qsl("globalX").toUtf8().constData());
// Push globalY()
lua_pushnumber(L, globalPosition.y());
lua_setfield(L, -2, qsl("globalY").toUtf8().constData());
// Push x()
lua_pushnumber(L, position.x());
lua_setfield(L, -2, qsl("x").toUtf8().constData());
// Push y()
lua_pushnumber(L, position.y());
lua_setfield(L, -2, qsl("y").toUtf8().constData());
return callReference(L, name, 1);
}
// These are QEvents
case (QEvent::Enter): {
auto qME = static_cast<const QEnterEvent*>(qE);
lua_newtable(L);
auto globalPosition = qME->globalPosition().toPoint();
auto position = qME->position().toPoint();
// Push globalX()
lua_pushnumber(L, globalPosition.x());
lua_setfield(L, -2, qsl("globalX").toUtf8().constData());
// Push globalY()
lua_pushnumber(L, globalPosition.y());
lua_setfield(L, -2, qsl("globalY").toUtf8().constData());
// Push x()
lua_pushnumber(L, position.x());
lua_setfield(L, -2, qsl("x").toUtf8().constData());
// Push y()
lua_pushnumber(L, position.y());
lua_setfield(L, -2, qsl("y").toUtf8().constData());
return callReference(L, name, 1);
}
case (QEvent::Leave): {
// Seems there isn't a QLeaveEvent, so no
// extra information to be gotten
return callReference(L, name, 0);
}
// This is a QWheelEvent
case (QEvent::Wheel): {
auto qME = static_cast<const QWheelEvent*>(qE);
lua_newtable(L);
// push buttons()
lua_newtable(L);
QMap<Qt::MouseButton, QString>::const_iterator iter = csmMouseButtons.constBegin();
int counter = 1;
while (iter != csmMouseButtons.constEnd()) {
if (iter.key() & qME->buttons()) {
lua_pushnumber(L, counter);
lua_pushstring(L, iter.value().toUtf8().constData());
lua_settable(L, -3);
counter++;
}
++iter;
}
lua_setfield(L, -2, qsl("buttons").toUtf8().constData());
auto globalPosition = qME->globalPosition();
// Push globalX()
lua_pushnumber(L, globalPosition.x());
lua_setfield(L, -2, qsl("globalX").toUtf8().constData());
// Push globalY()
lua_pushnumber(L, globalPosition.y());
lua_setfield(L, -2, qsl("globalY").toUtf8().constData());
auto position = qME->position();
// Push x()
lua_pushnumber(L, position.x());
lua_setfield(L, -2, qsl("x").toUtf8().constData());
// Push y()
lua_pushnumber(L, position.y());
lua_setfield(L, -2, qsl("y").toUtf8().constData());
// Push angleDelta()
lua_pushnumber(L, qME->angleDelta().x());
lua_setfield(L, -2, qsl("angleDeltaX").toUtf8().constData());
lua_pushnumber(L, qME->angleDelta().y());
lua_setfield(L, -2, qsl("angleDeltaY").toUtf8().constData());
return callReference(L, name, 1);
}
default: {
// No-op - this silences warnings about unhandled QEvent types
}
}
} else {
return callReference(L, name, 0);
}
lua_settop(L, callerStackTop);
return true;
}
// No documentation available in wiki - internal function
bool TLuaInterpreter::callEventHandler(const QString& function, const TEvent& pE)
{
if (function.isEmpty()) {
return false;
}
lua_State* L = pGlobalLua;
// Validate Lua state before attempting to call event handler
if (!L) {
qWarning() << "TLuaInterpreter::callEventHandler() - Lua state is null for function:" << function;
return false;
}
// Check if we're in emergency stop mode
if (mpHost && mpHost->mEmergencyStop) {
return false;
}
// Events are often raised from inside another Lua API C function, which is
// still holding its arguments and any already-pushed return values on this
// same stack, so only ever unwind back down to what we found:
const int initialStackSize = lua_gettop(L);
int error = luaL_dostring(L, qsl("return %1").arg(function).toUtf8().constData());
if (error) {
std::string err;
if (lua_isstring(L, -1)) {
err = "Lua error: ";
err += lua_tostring(L, -1);
}
const QString name = "event handler function";
logError(err, name, function);
// Clean up stack after error
lua_settop(L, initialStackSize);
return false;
}
// Lua is limited to ~50 arguments on a function
// Use minimum of argument list size and type list size to avoid out-of-bounds access
auto maxArguments = std::min({pE.mArgumentList.size(), pE.mArgumentTypeList.size(), static_cast<qsizetype>(LUA_FUNCTION_MAX_ARGS)});
if (pE.mArgumentList.size() != pE.mArgumentTypeList.size()) {
qWarning() << "TLuaInterpreter::callEventHandler() WARNING: argument list size" << pE.mArgumentList.size() << "does not match type list size" << pE.mArgumentTypeList.size()
<< "for function:" << function;
}
// A lua_CFunction is only guaranteed LUA_MINSTACK slots, and whatever C
// function we are nested inside is already using some of them:
if (!lua_checkstack(L, static_cast<int>(maxArguments) + 1)) {
std::string err = "could not grow the Lua stack to pass this event's arguments to the handler";
const QString name = "event handler function";
logError(err, name, function);
lua_settop(L, initialStackSize);
return false;
}
for (int i = 0; i < maxArguments; i++) {
switch (pE.mArgumentTypeList.at(i)) {
case ARGUMENT_TYPE_NUMBER:
lua_pushnumber(L, pE.mArgumentList.at(i).toDouble());
break;
case ARGUMENT_TYPE_STRING:
lua_pushstring(L, pE.mArgumentList.at(i).toUtf8().constData());
break;
case ARGUMENT_TYPE_BOOLEAN:
lua_pushboolean(L, pE.mArgumentList.at(i).toInt());
break;
case ARGUMENT_TYPE_NIL:
lua_pushnil(L);
break;
case ARGUMENT_TYPE_TABLE:
lua_rawgeti(L, LUA_REGISTRYINDEX, pE.mArgumentList.at(i).toInt());
break;
case ARGUMENT_TYPE_FUNCTION:
lua_rawgeti(L, LUA_REGISTRYINDEX, pE.mArgumentList.at(i).toInt());
break;
default:
qWarning(R"(TLuaInterpreter::callEventHandler("%s", TEvent) ERROR: Unhandled ARGUMENT_TYPE: %i encountered in argument %i.)", function.toUtf8().constData(), pE.mArgumentTypeList.at(i), i);
lua_pushnil(L);
}
}
error = lua_pcall(L, maxArguments, LUA_MULTRET, 0);
if (mudlet::smDebugMode && pE.mArgumentList.size() > LUA_FUNCTION_MAX_ARGS) {
auto& host = getHostFromLua(L);
TDebug(Qt::white, Qt::red) << "LUA: ERROR running script " << function << " (" << function << ")\nError: more than " << LUA_FUNCTION_MAX_ARGS
<< " arguments passed to Lua function, exceeding Lua's limit. Trimmed arguments to " << LUA_FUNCTION_MAX_ARGS << " only.\n"
>> &host;
}
if (error) {
std::string err = "";
if (lua_isstring(L, -1)) {
err += lua_tostring(L, -1);
}
const QString name = "event handler function";
logError(err, name, function);
if (mudlet::smDebugMode) {
auto& host = getHostFromLua(L);
TDebug(Qt::white, Qt::red) << "LUA: ERROR running script " << function << " (" << function << ")\nError: " << err.c_str() << "\n" >> &host;
}
}
lua_settop(L, initialStackSize);
return !error;
}
// No documentation available in wiki - internal, test-only helper for waitForEvent()
// Snapshots a TEvent's arguments into a fresh Lua table {[1]=name, [2]=arg, ...}
// with a numeric length in the "n" field, and returns a registry reference to
// it. Table/function arguments are anchored by this new table (it holds a
// reference to the same object the handlers saw), so they survive
// Host::raiseEvent() freeing the event's own registry entries, letting
// waitForEvent() hand the values back after the loop unwinds.
int TLuaInterpreter::createEventArgsTableRef(const TEvent& pE)
{
lua_State* L = pGlobalLua;
const int initialStackSize = lua_gettop(L);
const auto argCount = std::min(pE.mArgumentList.size(), pE.mArgumentTypeList.size());
lua_newtable(L);
for (qsizetype i = 0; i < argCount; ++i) {
switch (pE.mArgumentTypeList.at(i)) {
case ARGUMENT_TYPE_NUMBER:
lua_pushnumber(L, pE.mArgumentList.at(i).toDouble());
break;
case ARGUMENT_TYPE_STRING:
lua_pushstring(L, pE.mArgumentList.at(i).toUtf8().constData());
break;
case ARGUMENT_TYPE_BOOLEAN:
lua_pushboolean(L, pE.mArgumentList.at(i).toInt());
break;
case ARGUMENT_TYPE_NIL:
lua_pushnil(L);
break;
case ARGUMENT_TYPE_TABLE:
case ARGUMENT_TYPE_FUNCTION:
lua_rawgeti(L, LUA_REGISTRYINDEX, pE.mArgumentList.at(i).toInt());
break;
default:
lua_pushnil(L);
}
lua_rawseti(L, -2, static_cast<int>(i) + 1);
}
lua_pushinteger(L, argCount);
lua_setfield(L, -2, "n");
const int ref = luaL_ref(L, LUA_REGISTRYINDEX);
// luaL_ref popped the table; restore the stack exactly in case this runs
// nested inside another operation's stack.
lua_settop(L, initialStackSize);
return ref;
}
// No documentation available in wiki - internal, test-only helper for waitForEvent()
void TLuaInterpreter::captureEventForWaits(const TEvent& pE)
{
if (mPendingEventWaits.isEmpty() || pE.mArgumentList.isEmpty()) {
return;
}
const QString& eventName = pE.mArgumentList.at(0);
for (auto* pWait : mPendingEventWaits) {
if (pWait->mCaptured || pWait->mName != eventName) {
continue;
}
pWait->mArgsRef = createEventArgsTableRef(pE);
pWait->mCaptured = true;
}
}
// No documentation available in wiki - internal function
double TLuaInterpreter::condenseMapLoad()
{
QElapsedTimer executionTimer;
if (mudlet::smDebugMode) {
executionTimer.start();
}
const QString luaFunction = qsl("condenseMapLoad");
double loadTime = -1.0;
lua_State* L = pGlobalLua;
const int callerStackTop = lua_gettop(L);
lua_getfield(L, LUA_GLOBALSINDEX, "condenseMapLoad");
const int error = lua_pcall(L, 0, 1, 0);
if (error) {
std::string e = "";
if (lua_isstring(L, -1)) {
e += lua_tostring(L, -1);
const QString _f = luaFunction.toUtf8().constData();
logError(e, luaFunction, _f);
if (mudlet::smDebugMode) {
auto& host = getHostFromLua(L);
TDebug(Qt::white, Qt::red) << "LUA: ERROR running " << luaFunction << " ERROR:" << e.c_str() << "\n" >> &host;
}
}
} else {
if (mudlet::smDebugMode) {
auto& host = getHostFromLua(L);
TDebug(Qt::white, Qt::darkGreen) << "LUA OK " << luaFunction << " ran without errors in" << (executionTimer.isValid() ? " in " + QString::number(executionTimer.elapsed()) + "ms\n" : ".\n")
>> &host;
}
}
if (lua_gettop(L) > callerStackTop && !lua_isnoneornil(L, -1)) {
loadTime = lua_tonumber(L, -1);
}
lua_settop(L, callerStackTop);
return loadTime;
}
// No documentation available in wiki - internal function
// `verb` is a const char* so that customHTTP() need not own a heap buffer
// across the checks below - see checkStringArg()
int TLuaInterpreter::performHttpRequest(lua_State* L, const char* functionName, const int pos, QNetworkAccessManager::Operation operation, const char* verb)
{
auto& host = getHostFromLua(L);
if (!lua_isstring(L, pos + 1) && !lua_isstring(L, pos + 4)) {
lua_pushfstring(L, "%s: bad argument #%d type (data to send as string expected, got %s!)", functionName, pos + 1, luaL_typename(L, pos + 1));
return lua_error(L);
}
if (!checkStringArg(L, functionName, pos + 2, "remote url")) {
return lua_error(L);
}
validateHttpHeaders(L, pos + 3, functionName);
if (!lua_isstring(L, pos + 4) && !lua_isnoneornil(L, pos + 4)) {
lua_pushfstring(L, "%s: bad argument #%d type (file to send as string location expected, got %s!)", functionName, pos + 4, luaL_typename(L, pos + 4));
return lua_error(L);
}
QString dataToPost;
if (lua_isstring(L, pos + 1)) {
dataToPost = lua_tostring(L, pos + 1);
}
const QString urlString{lua_tostring(L, pos + 2)};
QString fileLocation;
if (lua_isstring(L, pos + 4)) {
fileLocation = lua_tostring(L, pos + 4);
}
const QUrl url = QUrl::fromUserInput(urlString);
if (!url.isValid()) {
return warnArgumentValue(L, functionName, qsl("url is invalid, reason: %1.").arg(url.errorString()));
}
QNetworkRequest request = QNetworkRequest(url);
mudlet::self()->setNetworkRequestDefaults(url, request);
applyHttpHeaders(L, pos + 3, request);
QByteArray fileToUpload;
if (!fileLocation.isEmpty()) {
QFile file(fileLocation);
if (!file.open(QFile::ReadOnly)) {
return warnArgumentValue(L, functionName, qsl("couldn't open '%1', is the location correct and do you have permissions to it?").arg(fileLocation));
}
fileToUpload = file.readAll();
file.close();
}
host.updateProxySettings(host.mLuaInterpreter.mpFileDownloader);
QNetworkReply* reply;
switch (operation) {
case QNetworkAccessManager::PostOperation:
reply = host.mLuaInterpreter.mpFileDownloader->post(request, fileToUpload.isEmpty() ? dataToPost.toUtf8() : fileToUpload);
break;
case QNetworkAccessManager::PutOperation:
reply = host.mLuaInterpreter.mpFileDownloader->put(request, fileToUpload.isEmpty() ? dataToPost.toUtf8() : fileToUpload);
break;
default:
reply = host.mLuaInterpreter.mpFileDownloader->sendCustomRequest(request, QByteArray{verb}, fileToUpload.isEmpty() ? dataToPost.toUtf8() : fileToUpload);
};
if (mudlet::smDebugMode) {
TDebug(Qt::white, Qt::blue) << functionName << ": script is uploading data to " << reply->url().toString() << "\n" >> &host;
}
lua_pushboolean(L, true);
lua_pushstring(L, reply->url().toString().toUtf8().constData()); // Returns the Url that was ACTUALLY used
return 2;
}
// Documentation: https://wiki.mudlet.org/w/Manual:Networking_Functions#unzipAsync
int TLuaInterpreter::unzipAsync(lua_State* L)
{
if (!checkStringArg(L, __func__, 1, "zip location") || !checkStringArg(L, __func__, 2, "extract location")) {
return lua_error(L);
}
const QString zipLocation{lua_tostring(L, 1)};
QString extractLocation{lua_tostring(L, 2)};
const QTemporaryDir temporaryDir;
if (!temporaryDir.isValid()) {
return warnArgumentValue(L, __func__, "couldn't create temporary directory to extract the zip into");
}
extractLocation = QDir::fromNativeSeparators(extractLocation);
if (!extractLocation.endsWith(QLatin1String("/"))) {
extractLocation.append(QLatin1String("/"));
}
const QDir dir;
if (!dir.mkpath(extractLocation)) {
return warnArgumentValue(L, __func__, "couldn't create output directory to put the extracted files into");
}
auto future = QtConcurrent::run(mudlet::unzip, zipLocation, extractLocation, temporaryDir.path());
auto watcher = new QFutureWatcher<bool>;
connect(watcher, &QFutureWatcher<bool>::finished, watcher, [=]() {
TEvent event{};
Host& host = getHostFromLua(L);
if (future.result()) {
event.mArgumentList.append(qsl("sysUnzipDone"));
event.mArgumentTypeList.append(ARGUMENT_TYPE_STRING);
} else {
event.mArgumentList.append(qsl("sysUnzipError"));
event.mArgumentTypeList.append(ARGUMENT_TYPE_STRING);
}
event.mArgumentList.append(zipLocation);
event.mArgumentTypeList.append(ARGUMENT_TYPE_STRING);
event.mArgumentList.append(extractLocation);
event.mArgumentTypeList.append(ARGUMENT_TYPE_STRING);
host.raiseEvent(event);
watcher->deleteLater();
});
watcher->setFuture(future);
lua_pushboolean(L, true);
return 1;
}
// No documentation available in wiki - internal function
void TLuaInterpreter::set_lua_table(const QString& tableName, QStringList& variableList)
{
lua_State* L = pGlobalLua;
const int callerStackTop = lua_gettop(L);
lua_newtable(L);
for (int i = 0; i < variableList.size(); i++) {
lua_pushnumber(L, i + 1); // Lua indexes start with 1
lua_pushstring(L, variableList[i].toUtf8().constData());
lua_settable(L, -3);
}
lua_setglobal(L, tableName.toUtf8().constData());
lua_settop(L, callerStackTop);
}
// No documentation available in wiki - internal function
void TLuaInterpreter::set_lua_string(const QString& varName, const QString& varValue)
{
lua_State* L = pGlobalLua;
const int callerStackTop = lua_gettop(L);
lua_pushstring(L, varValue.toUtf8().constData());
lua_setglobal(L, varName.toUtf8().constData());
lua_settop(L, callerStackTop);
}
// No documentation available in wiki - internal function
void TLuaInterpreter::set_lua_integer(const QString& varName, int varValue)
{
lua_State* L = pGlobalLua;
const int top = lua_gettop(L);
lua_pushnumber(L, varValue);
lua_setglobal(L, varName.toUtf8().constData());
lua_settop(L, top);
}
// No documentation available in wiki - internal function
QString TLuaInterpreter::getLuaString(const QString& stringName)
{
lua_State* L = pGlobalLua;
const int error = luaL_dostring(L, qsl("return %1").arg(stringName).toUtf8().constData());
if (!error) {
return lua_tostring(L, 1);
}
return QString();
}
// check for <whitespace><no_valid_representation> as output
// Documentation: https://wiki.mudlet.org/w/Manual:Lua_Functions#handleWindowResizeEvent is using noop function publicly - compare initLuaGlobals()
int TLuaInterpreter::noop(lua_State* L)
{
Q_UNUSED(L)
return 0;
}
// No documentation available in wiki - internal function
int TLuaInterpreter::check_for_mappingscript()
{
lua_State* L = pGlobalLua;
lua_getglobal(L, "mudlet");
if (!lua_istable(L, -1)) {
lua_pop(L, 1);
return 0;
}
lua_getfield(L, -1, "mapper_script");
if (!lua_isboolean(L, -1)) {
lua_pop(L, 2);
return 0;
}
const int r = lua_toboolean(L, -1);
lua_pop(L, 2);
return r;
}
// No documentation available in wiki - internal function
int TLuaInterpreter::check_for_custom_speedwalk()
{
lua_State* L = pGlobalLua;
lua_getglobal(L, "mudlet");
if (!lua_istable(L, -1)) {
lua_pop(L, 1);
return 0;
}
lua_getfield(L, -1, "custom_speedwalk");
if (!lua_isboolean(L, -1)) {
lua_pop(L, 2);
return 0;
}
const int r = lua_toboolean(L, -1);
lua_pop(L, 2);
return r;
}
// No documentation available in wiki - internal function
static lua_State* newstate()
{
return luaL_newstate();
}
// No documentation available in wiki - internal function
static void storeHostInLua(lua_State* L, Host* h);
// No documentation available in wiki - internal helper function
// On success will swap out any messages in the queue and replace them
// with its success message, on failure will just append...
bool TLuaInterpreter::loadLuaModule(QQueue<QString>& resultMsgsQueue, const QString& requirement, const QString& failureConsequence, const QString& description, const QString& luaModuleId)
{
const int error = luaL_dostring(pGlobalLua, qsl("%1require \"%2\"").arg(luaModuleId.isEmpty() ? QString() : qsl("%1 =").arg(luaModuleId), requirement).toUtf8().constData());
if (error) {
QString luaErrorMsg = tr("No error message available from Lua");
if (lua_isstring(pGlobalLua, -1)) {
luaErrorMsg = tr("Lua error: %1").arg(lua_tostring(pGlobalLua, -1));
}
/*:
%1 is the name of the module;
%2 will be a line-feed inserted to put the next argument on a new line;
%3 is the error message from the lua sub-system;
%4 can be an additional message about the expected effect (but may be blank).
*/
resultMsgsQueue.enqueue(
tr("[ ERROR ] - Cannot find Lua module %1.%2%3%4")
.arg((description.isEmpty() ? requirement : description), QLatin1String("\n"), luaErrorMsg, (failureConsequence.isEmpty() ? QString() : qsl("\n%1").arg(failureConsequence))));
return false;
}
// clear out the queue in case any previous attempts to load the module of the same name failed
QQueue<QString> newMessageQueue;
// uncomment to show Lua module messages for debugging
// newMessageQueue.enqueue(tr("[ OK ] - Lua module %1 loaded.",
// "%1 is the name (may specify which variant) of the module.")
// .arg(description.isEmpty() ? requirement : description));
resultMsgsQueue.swap(newMessageQueue);
return true;
}
// No documentation available in wiki - internal function
void TLuaInterpreter::insertNativeSeparatorsFunction(lua_State* L)
{
// This is likely only needed for Windows but should not be harmful for
// other OSes and it keeps things simpler if they all use it:
// clang-format off
luaL_dostring(L, R"LUA(function toNativeSeparators(rawPath)
if package.config:sub(1,1) == '\\' then
return string.gsub(rawPath, '/', '\\')
end
assert((package.config:sub(1,1) == '/'), "package path directory separator is neither '\\' nor '/' and cannot be handled")
return string.gsub(rawPath, '\\', '/')
end)LUA");
// clang-format on
}
// Abort all in-flight network downloads during resetProfile so that
// stale completion/error events from the old Lua state are not
// delivered to the new state's event handlers.
void TLuaInterpreter::abortAllDownloads()
{
for (auto* reply : downloadMap.keys()) {
reply->abort();
}
downloadMap.clear();
}
// No documentation available in wiki - internal function
// This function initializes the main Lua Session interpreter.
// on initialization of a new session *or* in case of an interpreter reset by the user.
void TLuaInterpreter::initLuaGlobals()
{
if (pGlobalLua) {
lua_close(pGlobalLua);
}
pGlobalLua = newstate();
storeHostInLua(pGlobalLua, mpHost);
luaL_openlibs(pGlobalLua);
lua_pushstring(pGlobalLua, "SESSION");
lua_pushnumber(pGlobalLua, mHostID);
lua_settable(pGlobalLua, LUA_GLOBALSINDEX);
lua_pushstring(pGlobalLua, "SCRIPT_NAME");
lua_pushstring(pGlobalLua, "Global Lua Session Interpreter");
lua_settable(pGlobalLua, LUA_GLOBALSINDEX);
lua_pushstring(pGlobalLua, "SCRIPT_ID");
lua_pushnumber(pGlobalLua, -1); // ID 1 is used to indicate that this is the global Lua interpreter
lua_settable(pGlobalLua, LUA_GLOBALSINDEX);
lua_register(pGlobalLua, "showUnzipProgress", TLuaInterpreter::showUnzipProgress); //internal function used by the package system NOT FOR USERS
lua_register(pGlobalLua, "wait", TLuaInterpreter::Wait);
lua_register(pGlobalLua, "expandAlias", TLuaInterpreter::expandAlias);
lua_register(pGlobalLua, "echo", TLuaInterpreter::echo);
lua_register(pGlobalLua, "selectString", TLuaInterpreter::selectString);
lua_register(pGlobalLua, "selectSection", TLuaInterpreter::selectSection);
lua_register(pGlobalLua, "replace", TLuaInterpreter::replace);
lua_register(pGlobalLua, "setBgColor", TLuaInterpreter::setBgColor);
lua_register(pGlobalLua, "setFgColor", TLuaInterpreter::setFgColor);
lua_register(pGlobalLua, "tempTimer", TLuaInterpreter::tempTimer);
lua_register(pGlobalLua, "tempTrigger", TLuaInterpreter::tempTrigger);
lua_register(pGlobalLua, "tempRegexTrigger", TLuaInterpreter::tempRegexTrigger);
lua_register(pGlobalLua, "closeMudlet", TLuaInterpreter::closeMudlet);
lua_register(pGlobalLua, "loadWindowLayout", TLuaInterpreter::loadWindowLayout);
lua_register(pGlobalLua, "saveWindowLayout", TLuaInterpreter::saveWindowLayout);
lua_register(pGlobalLua, "setFont", TLuaInterpreter::setFont);
lua_register(pGlobalLua, "getFont", TLuaInterpreter::getFont);
lua_register(pGlobalLua, "setFontSize", TLuaInterpreter::setFontSize);
lua_register(pGlobalLua, "getFontSize", TLuaInterpreter::getFontSize);
lua_register(pGlobalLua, "openUserWindow", TLuaInterpreter::openUserWindow);
lua_register(pGlobalLua, "setUserWindowTitle", TLuaInterpreter::setUserWindowTitle);
lua_register(pGlobalLua, "getUserWindowTitle", TLuaInterpreter::getUserWindowTitle);
lua_register(pGlobalLua, "echoUserWindow", TLuaInterpreter::echoUserWindow);
lua_register(pGlobalLua, "enableTimer", TLuaInterpreter::enableTimer);
lua_register(pGlobalLua, "disableTimer", TLuaInterpreter::disableTimer);
lua_register(pGlobalLua, "enableKey", TLuaInterpreter::enableKey);
lua_register(pGlobalLua, "disableKey", TLuaInterpreter::disableKey);
lua_register(pGlobalLua, "killKey", TLuaInterpreter::killKey);
lua_register(pGlobalLua, "clearUserWindow", TLuaInterpreter::clearUserWindow);
lua_register(pGlobalLua, "clearWindow", TLuaInterpreter::clearUserWindow);
lua_register(pGlobalLua, "killTimer", TLuaInterpreter::killTimer);
lua_register(pGlobalLua, "remainingTime", TLuaInterpreter::remainingTime);
lua_register(pGlobalLua, "moveCursor", TLuaInterpreter::moveCursor);
lua_register(pGlobalLua, "getLines", TLuaInterpreter::getLines);
lua_register(pGlobalLua, "getLineNumber", TLuaInterpreter::getLineNumber);
lua_register(pGlobalLua, "insertHTML", TLuaInterpreter::insertHTML);
lua_register(pGlobalLua, "insertText", TLuaInterpreter::insertText);
lua_register(pGlobalLua, "enableTrigger", TLuaInterpreter::enableTrigger);
lua_register(pGlobalLua, "disableTrigger", TLuaInterpreter::disableTrigger);
lua_register(pGlobalLua, "enableScript", TLuaInterpreter::enableScript);
lua_register(pGlobalLua, "disableScript", TLuaInterpreter::disableScript);
lua_register(pGlobalLua, "killTrigger", TLuaInterpreter::killTrigger);
lua_register(pGlobalLua, "getLineCount", TLuaInterpreter::getLineCount);
lua_register(pGlobalLua, "getColumnNumber", TLuaInterpreter::getColumnNumber);
lua_register(pGlobalLua, "sendCmdLine", TLuaInterpreter::sendCmdLine);
lua_register(pGlobalLua, "send", TLuaInterpreter::sendRaw);
lua_register(pGlobalLua, "selectCaptureGroup", TLuaInterpreter::selectCaptureGroup);
lua_register(pGlobalLua, "tempLineTrigger", TLuaInterpreter::tempLineTrigger);
lua_register(pGlobalLua, "raiseEvent", TLuaInterpreter::raiseEvent);
lua_register(pGlobalLua, "waitForEvent", TLuaInterpreter::waitForEvent);
lua_register(pGlobalLua, "pumpEvents", TLuaInterpreter::pumpEvents);
lua_register(pGlobalLua, "deleteLine", TLuaInterpreter::deleteLine);
lua_register(pGlobalLua, "copy", TLuaInterpreter::copy);
lua_register(pGlobalLua, "cut", TLuaInterpreter::cut);
lua_register(pGlobalLua, "paste", TLuaInterpreter::paste);
lua_register(pGlobalLua, "pasteWindow", TLuaInterpreter::pasteWindow);
lua_register(pGlobalLua, "debugc", TLuaInterpreter::debug);
lua_register(pGlobalLua, "errorc", TLuaInterpreter::errorc);
lua_register(pGlobalLua, "showHandlerError", TLuaInterpreter::showHandlerError);
lua_register(pGlobalLua, "setWindowWrap", TLuaInterpreter::setWindowWrap);
lua_register(pGlobalLua, "getWindowWrap", TLuaInterpreter::getWindowWrap);
lua_register(pGlobalLua, "setWindowWrapIndent", TLuaInterpreter::setWindowWrapIndent);
lua_register(pGlobalLua, "setWindowWrapHangingIndent", TLuaInterpreter::setWindowWrapHangingIndent);
lua_register(pGlobalLua, "resetFormat", TLuaInterpreter::resetFormat);
lua_register(pGlobalLua, "moveCursorEnd", TLuaInterpreter::moveCursorEnd);
lua_register(pGlobalLua, "getLastLineNumber", TLuaInterpreter::getLastLineNumber);
lua_register(pGlobalLua, "getNetworkLatency", TLuaInterpreter::getNetworkLatency);
lua_register(pGlobalLua, "createMiniConsole", TLuaInterpreter::createMiniConsole);
lua_register(pGlobalLua, "createScrollBox", TLuaInterpreter::createScrollBox);
lua_register(pGlobalLua, "createLabel", TLuaInterpreter::createLabel);
lua_register(pGlobalLua, "deleteLabel", TLuaInterpreter::deleteLabel);
lua_register(pGlobalLua, "deleteMiniConsole", TLuaInterpreter::deleteMiniConsole);
lua_register(pGlobalLua, "deleteCommandLine", TLuaInterpreter::deleteCommandLine);
lua_register(pGlobalLua, "createTextEdit", TLuaInterpreter::createTextEdit);
lua_register(pGlobalLua, "deleteTextEdit", TLuaInterpreter::deleteTextEdit);
lua_register(pGlobalLua, "getTextEditText", TLuaInterpreter::getTextEditText);
lua_register(pGlobalLua, "setTextEditText", TLuaInterpreter::setTextEditText);
lua_register(pGlobalLua, "clearTextEdit", TLuaInterpreter::clearTextEdit);
lua_register(pGlobalLua, "setTextEditReadOnly", TLuaInterpreter::setTextEditReadOnly);
lua_register(pGlobalLua, "setTextEditPlaceholder", TLuaInterpreter::setTextEditPlaceholder);
lua_register(pGlobalLua, "setTextEditStyleSheet", TLuaInterpreter::setTextEditStyleSheet);
lua_register(pGlobalLua, "setTextEditFont", TLuaInterpreter::setTextEditFont);
lua_register(pGlobalLua, "setTextEditFontSize", TLuaInterpreter::setTextEditFontSize);
lua_register(pGlobalLua, "setTextEditTabMovesFocus", TLuaInterpreter::setTextEditTabMovesFocus);
lua_register(pGlobalLua, "deleteScrollBox", TLuaInterpreter::deleteScrollBox);
lua_register(pGlobalLua, "setLabelToolTip", TLuaInterpreter::setLabelToolTip);
lua_register(pGlobalLua, "getLabelToolTip", TLuaInterpreter::getLabelToolTip);
lua_register(pGlobalLua, "setLabelCursor", TLuaInterpreter::setLabelCursor);
lua_register(pGlobalLua, "setLabelCustomCursor", TLuaInterpreter::setLabelCustomCursor);
lua_register(pGlobalLua, "raiseWindow", TLuaInterpreter::raiseWindow);
lua_register(pGlobalLua, "lowerWindow", TLuaInterpreter::lowerWindow);
lua_register(pGlobalLua, "hideWindow", TLuaInterpreter::hideWindow);
lua_register(pGlobalLua, "showWindow", TLuaInterpreter::showWindow);
lua_register(pGlobalLua, "createBuffer", TLuaInterpreter::createBuffer);
lua_register(pGlobalLua, "createStopWatch", TLuaInterpreter::createStopWatch);
lua_register(pGlobalLua, "getStopWatchTime", TLuaInterpreter::getStopWatchTime);
lua_register(pGlobalLua, "stopStopWatch", TLuaInterpreter::stopStopWatch);
lua_register(pGlobalLua, "startStopWatch", TLuaInterpreter::startStopWatch);
lua_register(pGlobalLua, "resetStopWatch", TLuaInterpreter::resetStopWatch);
lua_register(pGlobalLua, "adjustStopWatch", TLuaInterpreter::adjustStopWatch);
lua_register(pGlobalLua, "deleteStopWatch", TLuaInterpreter::deleteStopWatch);
lua_register(pGlobalLua, "setStopWatchPersistence", TLuaInterpreter::setStopWatchPersistence);
lua_register(pGlobalLua, "getStopWatches", TLuaInterpreter::getStopWatches);
lua_register(pGlobalLua, "setStopWatchName", TLuaInterpreter::setStopWatchName);
lua_register(pGlobalLua, "getStopWatchBrokenDownTime", TLuaInterpreter::getStopWatchBrokenDownTime);
lua_register(pGlobalLua, "closeUserWindow", TLuaInterpreter::closeUserWindow);
lua_register(pGlobalLua, "resizeWindow", TLuaInterpreter::resizeWindow);
lua_register(pGlobalLua, "appendBuffer", TLuaInterpreter::appendBuffer);
lua_register(pGlobalLua, "setBackgroundImage", TLuaInterpreter::setBackgroundImage);
lua_register(pGlobalLua, "resetBackgroundImage", TLuaInterpreter::resetBackgroundImage);
lua_register(pGlobalLua, "setBackgroundColor", TLuaInterpreter::setBackgroundColor);
lua_register(pGlobalLua, "setCommandBackgroundColor", TLuaInterpreter::setCommandBackgroundColor);
lua_register(pGlobalLua, "setCommandForegroundColor", TLuaInterpreter::setCommandForegroundColor);
lua_register(pGlobalLua, "setCmdLineAction", TLuaInterpreter::setCmdLineAction);
lua_register(pGlobalLua, "resetCmdLineAction", TLuaInterpreter::resetCmdLineAction);
lua_register(pGlobalLua, "setCmdLineStyleSheet", TLuaInterpreter::setCmdLineStyleSheet);
lua_register(pGlobalLua, "getCmdLineStyleSheet", TLuaInterpreter::getCmdLineStyleSheet);
lua_register(pGlobalLua, "setLabelClickCallback", TLuaInterpreter::setLabelClickCallback);
lua_register(pGlobalLua, "setLabelDoubleClickCallback", TLuaInterpreter::setLabelDoubleClickCallback);
lua_register(pGlobalLua, "setLabelReleaseCallback", TLuaInterpreter::setLabelReleaseCallback);
lua_register(pGlobalLua, "setLabelMoveCallback", TLuaInterpreter::setLabelMoveCallback);
lua_register(pGlobalLua, "setLabelWheelCallback", TLuaInterpreter::setLabelWheelCallback);
lua_register(pGlobalLua, "setLabelOnEnter", TLuaInterpreter::setLabelOnEnter);
lua_register(pGlobalLua, "setLabelOnLeave", TLuaInterpreter::setLabelOnLeave);
lua_register(pGlobalLua, "setMovie", TLuaInterpreter::setMovie);
lua_register(pGlobalLua, "startMovie", TLuaInterpreter::startMovie);
lua_register(pGlobalLua, "setMovieSpeed", TLuaInterpreter::setMovieSpeed);
lua_register(pGlobalLua, "scaleMovie", TLuaInterpreter::scaleMovie);
lua_register(pGlobalLua, "setMovieFrame", TLuaInterpreter::setMovieFrame);
lua_register(pGlobalLua, "pauseMovie", TLuaInterpreter::pauseMovie);
lua_register(pGlobalLua, "getImageSize", TLuaInterpreter::getImageSize);
lua_register(pGlobalLua, "moveWindow", TLuaInterpreter::moveWindow);
lua_register(pGlobalLua, "setWindow", TLuaInterpreter::setWindow);
lua_register(pGlobalLua, "openMapWidget", TLuaInterpreter::openMapWidget);
lua_register(pGlobalLua, "closeMapWidget", TLuaInterpreter::closeMapWidget);
lua_register(pGlobalLua, "getMapWidgetGeometry", TLuaInterpreter::getMapWidgetGeometry);
lua_register(pGlobalLua, "setTextFormat", TLuaInterpreter::setTextFormat);
lua_register(pGlobalLua, "getMainWindowSize", TLuaInterpreter::getMainWindowSize);
lua_register(pGlobalLua, "getUserWindowSize", TLuaInterpreter::getUserWindowSize);
lua_register(pGlobalLua, "getWindowGeometry", TLuaInterpreter::getWindowGeometry);
lua_register(pGlobalLua, "windowVisible", TLuaInterpreter::windowVisible);
lua_register(pGlobalLua, "getLabelText", TLuaInterpreter::getLabelText);
lua_register(pGlobalLua, "getMousePosition", TLuaInterpreter::getMousePosition);
lua_register(pGlobalLua, "setProfileIcon", TLuaInterpreter::setProfileIcon);
lua_register(pGlobalLua, "resetProfileIcon", TLuaInterpreter::resetProfileIcon);
lua_register(pGlobalLua, "getCurrentLine", TLuaInterpreter::getCurrentLine);
lua_register(pGlobalLua, "setMiniConsoleFontSize", TLuaInterpreter::setFontSize);
lua_register(pGlobalLua, "selectCurrentLine", TLuaInterpreter::selectCurrentLine);
lua_register(pGlobalLua, "spawn", TLuaInterpreter::spawn);
lua_register(pGlobalLua, "getButtonState", TLuaInterpreter::getButtonState);
lua_register(pGlobalLua, "setButtonState", TLuaInterpreter::setButtonState);
lua_register(pGlobalLua, "showToolBar", TLuaInterpreter::showToolBar);
lua_register(pGlobalLua, "hideToolBar", TLuaInterpreter::hideToolBar);
lua_register(pGlobalLua, "loadRawFile", TLuaInterpreter::loadReplay);
lua_register(pGlobalLua, "loadReplay", TLuaInterpreter::loadReplay);
lua_register(pGlobalLua, "setBold", TLuaInterpreter::setBold);
lua_register(pGlobalLua, "setItalics", TLuaInterpreter::setItalics);
lua_register(pGlobalLua, "setOverline", TLuaInterpreter::setOverline);
lua_register(pGlobalLua, "setReverse", TLuaInterpreter::setReverse);
lua_register(pGlobalLua, "setStrikeOut", TLuaInterpreter::setStrikeOut);
lua_register(pGlobalLua, "setUnderline", TLuaInterpreter::setUnderline);
lua_register(pGlobalLua, "disconnect", TLuaInterpreter::disconnect);
lua_register(pGlobalLua, "tempButtonToolbar", TLuaInterpreter::tempButtonToolbar);
lua_register(pGlobalLua, "tempButton", TLuaInterpreter::tempButton);
lua_register(pGlobalLua, "setButtonStyleSheet", TLuaInterpreter::setButtonStyleSheet);
lua_register(pGlobalLua, "reconnect", TLuaInterpreter::reconnect);
lua_register(pGlobalLua, "getMudletHomeDir", TLuaInterpreter::getMudletHomeDir);
lua_register(pGlobalLua, "setTriggerStayOpen", TLuaInterpreter::setTriggerStayOpen);
lua_register(pGlobalLua, "wrapLine", TLuaInterpreter::wrapLine);
lua_register(pGlobalLua, "getFgColor", TLuaInterpreter::getFgColor);
lua_register(pGlobalLua, "getBgColor", TLuaInterpreter::getBgColor);
lua_register(pGlobalLua, "tempColorTrigger", TLuaInterpreter::tempColorTrigger);
lua_register(pGlobalLua, "isAnsiFgColor", TLuaInterpreter::isAnsiFgColor);
lua_register(pGlobalLua, "isAnsiBgColor", TLuaInterpreter::isAnsiBgColor);
lua_register(pGlobalLua, "receiveMSP", TLuaInterpreter::receiveMSP);
lua_register(pGlobalLua, "loadSoundFile", TLuaInterpreter::loadSoundFile);
lua_register(pGlobalLua, "loadMusicFile", TLuaInterpreter::loadMusicFile);
lua_register(pGlobalLua, "loadVideoFile", TLuaInterpreter::loadVideoFile);
lua_register(pGlobalLua, "playSoundFile", TLuaInterpreter::playSoundFile);
lua_register(pGlobalLua, "playMusicFile", TLuaInterpreter::playMusicFile);
lua_register(pGlobalLua, "playVideoFile", TLuaInterpreter::playVideoFile);
lua_register(pGlobalLua, "getPlayingMusic", TLuaInterpreter::getPlayingMusic);
lua_register(pGlobalLua, "getPlayingSounds", TLuaInterpreter::getPlayingSounds);
lua_register(pGlobalLua, "getPlayingVideos", TLuaInterpreter::getPlayingVideos);
lua_register(pGlobalLua, "getPausedSounds", TLuaInterpreter::getPausedSounds);
lua_register(pGlobalLua, "getPausedMusic", TLuaInterpreter::getPausedMusic);
lua_register(pGlobalLua, "getPausedVideos", TLuaInterpreter::getPausedVideos);
lua_register(pGlobalLua, "stopMusic", TLuaInterpreter::stopMusic);
lua_register(pGlobalLua, "stopSounds", TLuaInterpreter::stopSounds);
lua_register(pGlobalLua, "stopVideos", TLuaInterpreter::stopVideos);
lua_register(pGlobalLua, "pauseSounds", TLuaInterpreter::pauseSounds);
lua_register(pGlobalLua, "pauseMusic", TLuaInterpreter::pauseMusic);
lua_register(pGlobalLua, "pauseVideos", TLuaInterpreter::pauseVideos);
lua_register(pGlobalLua, "purgeMediaCache", TLuaInterpreter::purgeMediaCache);
lua_register(pGlobalLua, "setBorderSizes", TLuaInterpreter::setBorderSizes);
lua_register(pGlobalLua, "setBorderTop", TLuaInterpreter::setBorderTop);
lua_register(pGlobalLua, "setBorderRight", TLuaInterpreter::setBorderRight);
lua_register(pGlobalLua, "setBorderBottom", TLuaInterpreter::setBorderBottom);
lua_register(pGlobalLua, "setBorderLeft", TLuaInterpreter::setBorderLeft);
lua_register(pGlobalLua, "setBorderColor", TLuaInterpreter::setBorderColor);
lua_register(pGlobalLua, "getBorderTop", TLuaInterpreter::getBorderTop);
lua_register(pGlobalLua, "getBorderRight", TLuaInterpreter::getBorderRight);
lua_register(pGlobalLua, "getBorderBottom", TLuaInterpreter::getBorderBottom);
lua_register(pGlobalLua, "getBorderLeft", TLuaInterpreter::getBorderLeft);
lua_register(pGlobalLua, "getBorderSizes", TLuaInterpreter::getBorderSizes);
lua_register(pGlobalLua, "getBorderColor", TLuaInterpreter::getBorderColor);
lua_register(pGlobalLua, "getConsoleBufferSize", TLuaInterpreter::getConsoleBufferSize);
lua_register(pGlobalLua, "setConsoleBufferSize", TLuaInterpreter::setConsoleBufferSize);
lua_register(pGlobalLua, "enableScrollBar", TLuaInterpreter::enableScrollBar);
lua_register(pGlobalLua, "disableScrollBar", TLuaInterpreter::disableScrollBar);
lua_register(pGlobalLua, "getScrollBarVisible", TLuaInterpreter::getScrollBarVisible);
lua_register(pGlobalLua, "enableHorizontalScrollBar", TLuaInterpreter::enableHorizontalScrollBar);
lua_register(pGlobalLua, "disableHorizontalScrollBar", TLuaInterpreter::disableHorizontalScrollBar);
lua_register(pGlobalLua, "enableCommandLine", TLuaInterpreter::enableCommandLine);
lua_register(pGlobalLua, "disableCommandLine", TLuaInterpreter::disableCommandLine);
lua_register(pGlobalLua, "startLogging", TLuaInterpreter::startLogging);
lua_register(pGlobalLua, "appendLog", TLuaInterpreter::appendLog);
lua_register(pGlobalLua, "calcFontSize", TLuaInterpreter::calcFontSize);
lua_register(pGlobalLua, "permRegexTrigger", TLuaInterpreter::permRegexTrigger);
lua_register(pGlobalLua, "permSubstringTrigger", TLuaInterpreter::permSubstringTrigger);
lua_register(pGlobalLua, "permExactMatchTrigger", TLuaInterpreter::permExactMatchTrigger);
lua_register(pGlobalLua, "permBeginOfLineStringTrigger", TLuaInterpreter::permBeginOfLineStringTrigger);
lua_register(pGlobalLua, "tempComplexRegexTrigger", TLuaInterpreter::tempComplexRegexTrigger);
lua_register(pGlobalLua, "permTimer", TLuaInterpreter::permTimer);
lua_register(pGlobalLua, "permScript", TLuaInterpreter::permScript);
lua_register(pGlobalLua, "getScript", TLuaInterpreter::getScript);
lua_register(pGlobalLua, "setScript", TLuaInterpreter::setScript);
lua_register(pGlobalLua, "permAlias", TLuaInterpreter::permAlias);
lua_register(pGlobalLua, "permKey", TLuaInterpreter::permKey);
lua_register(pGlobalLua, "tempKey", TLuaInterpreter::tempKey);
lua_register(pGlobalLua, "exists", TLuaInterpreter::exists);
lua_register(pGlobalLua, "isActive", TLuaInterpreter::isActive);
lua_register(pGlobalLua, "isAncestorsActive", TLuaInterpreter::isAncestorsActive);
lua_register(pGlobalLua, "ancestors", TLuaInterpreter::ancestors);
lua_register(pGlobalLua, "enableAlias", TLuaInterpreter::enableAlias);
lua_register(pGlobalLua, "tempAlias", TLuaInterpreter::tempAlias);
lua_register(pGlobalLua, "disableAlias", TLuaInterpreter::disableAlias);
lua_register(pGlobalLua, "killAlias", TLuaInterpreter::killAlias);
lua_register(pGlobalLua, "setLabelStyleSheet", TLuaInterpreter::setLabelStyleSheet);
lua_register(pGlobalLua, "setUserWindowStyleSheet", TLuaInterpreter::setUserWindowStyleSheet);
lua_register(pGlobalLua, "getUserWindowStyleSheet", TLuaInterpreter::getUserWindowStyleSheet);
lua_register(pGlobalLua, "getTime", TLuaInterpreter::getTime);
lua_register(pGlobalLua, "getEpoch", TLuaInterpreter::getEpoch);
lua_register(pGlobalLua, "invokeFileDialog", TLuaInterpreter::invokeFileDialog);
lua_register(pGlobalLua, "getTimestamp", TLuaInterpreter::getTimestamp);
lua_register(pGlobalLua, "setLink", TLuaInterpreter::setLink);
lua_register(pGlobalLua, "deselect", TLuaInterpreter::deselect);
lua_register(pGlobalLua, "insertLink", TLuaInterpreter::insertLink);
lua_register(pGlobalLua, "echoLink", TLuaInterpreter::echoLink);
lua_register(pGlobalLua, "echoPopup", TLuaInterpreter::echoPopup);
lua_register(pGlobalLua, "insertPopup", TLuaInterpreter::insertPopup);
lua_register(pGlobalLua, "setPopup", TLuaInterpreter::setPopup);
lua_register(pGlobalLua, "sendATCP", TLuaInterpreter::sendATCP);
lua_register(pGlobalLua, "hasFocus", TLuaInterpreter::hasFocus);
lua_register(pGlobalLua, "isPrompt", TLuaInterpreter::isPrompt);
lua_register(pGlobalLua, "feedTelnet", TLuaInterpreter::feedTelnet);
lua_register(pGlobalLua, "feedTriggers", TLuaInterpreter::feedTriggers);
lua_register(pGlobalLua, "sendTelnetChannel102", TLuaInterpreter::sendTelnetChannel102);
lua_register(pGlobalLua, "setRoomWeight", TLuaInterpreter::setRoomWeight);
lua_register(pGlobalLua, "getRoomWeight", TLuaInterpreter::getRoomWeight);
lua_register(pGlobalLua, "setRoomHidden", TLuaInterpreter::setRoomHidden);
lua_register(pGlobalLua, "getRoomHidden", TLuaInterpreter::getRoomHidden);
lua_register(pGlobalLua, "getHiddenRooms", TLuaInterpreter::getHiddenRooms);
lua_register(pGlobalLua, "gotoRoom", TLuaInterpreter::gotoRoom);
lua_register(pGlobalLua, "getRoomExits", TLuaInterpreter::getRoomExits);
lua_register(pGlobalLua, "lockRoom", TLuaInterpreter::lockRoom);
lua_register(pGlobalLua, "createMapper", TLuaInterpreter::createMapper);
lua_register(pGlobalLua, "createCommandLine", TLuaInterpreter::createCommandLine);
lua_register(pGlobalLua, "getMainConsoleWidth", TLuaInterpreter::getMainConsoleWidth);
lua_register(pGlobalLua, "resetProfile", TLuaInterpreter::resetProfile);
lua_register(pGlobalLua, "printCmdLine", TLuaInterpreter::printCmdLine);
lua_register(pGlobalLua, "searchRoom", TLuaInterpreter::searchRoom);
lua_register(pGlobalLua, "clearCmdLine", TLuaInterpreter::clearCmdLine);
lua_register(pGlobalLua, "getAreaTable", TLuaInterpreter::getAreaTable);
lua_register(pGlobalLua, "getAreaTableSwap", TLuaInterpreter::getAreaTableSwap);
lua_register(pGlobalLua, "getAreaRooms", TLuaInterpreter::getAreaRooms);
lua_register(pGlobalLua, "getAreaRooms1", TLuaInterpreter::getAreaRooms1);
lua_register(pGlobalLua, "getPath", TLuaInterpreter::getPath);
#if defined(INCLUDE_3DMAPPER)
lua_register(pGlobalLua, "shiftMapPerspective", TLuaInterpreter::shiftMapPerspective);
lua_register(pGlobalLua, "setMapPerspective", TLuaInterpreter::setMapPerspective);
#endif
lua_register(pGlobalLua, "centerview", TLuaInterpreter::centerview);
lua_register(pGlobalLua, "createMapView", TLuaInterpreter::createMapView);
lua_register(pGlobalLua, "closeMapView", TLuaInterpreter::closeMapView);
lua_register(pGlobalLua, "closeAllMapViews", TLuaInterpreter::closeAllMapViews);
lua_register(pGlobalLua, "getMapViewIds", TLuaInterpreter::getMapViewIds);
lua_register(pGlobalLua, "getMapViewInfo", TLuaInterpreter::getMapViewInfo);
lua_register(pGlobalLua, "denyCurrentSend", TLuaInterpreter::denyCurrentSend);
lua_register(pGlobalLua, "tempBeginOfLineTrigger", TLuaInterpreter::tempBeginOfLineTrigger);
lua_register(pGlobalLua, "tempExactMatchTrigger", TLuaInterpreter::tempExactMatchTrigger);
lua_register(pGlobalLua, "sendGMCP", TLuaInterpreter::sendGMCP);
lua_register(pGlobalLua, "roomExists", TLuaInterpreter::roomExists);
lua_register(pGlobalLua, "addRoom", TLuaInterpreter::addRoom);
lua_register(pGlobalLua, "setExit", TLuaInterpreter::setExit);
lua_register(pGlobalLua, "setRoomCoordinates", TLuaInterpreter::setRoomCoordinates);
lua_register(pGlobalLua, "getRoomCoordinates", TLuaInterpreter::getRoomCoordinates);
lua_register(pGlobalLua, "createRoomID", TLuaInterpreter::createRoomID);
lua_register(pGlobalLua, "getRoomArea", TLuaInterpreter::getRoomArea);
lua_register(pGlobalLua, "setRoomArea", TLuaInterpreter::setRoomArea);
lua_register(pGlobalLua, "resetRoomArea", TLuaInterpreter::resetRoomArea);
lua_register(pGlobalLua, "setAreaName", TLuaInterpreter::setAreaName);
lua_register(pGlobalLua, "roomLocked", TLuaInterpreter::roomLocked);
lua_register(pGlobalLua, "setCustomEnvColor", TLuaInterpreter::setCustomEnvColor);
lua_register(pGlobalLua, "getCustomEnvColorTable", TLuaInterpreter::getCustomEnvColorTable);
lua_register(pGlobalLua, "setRoomEnv", TLuaInterpreter::setRoomEnv);
lua_register(pGlobalLua, "setRoomName", TLuaInterpreter::setRoomName);
lua_register(pGlobalLua, "getRoomName", TLuaInterpreter::getRoomName);
lua_register(pGlobalLua, "setGridMode", TLuaInterpreter::setGridMode);
lua_register(pGlobalLua, "getGridMode", TLuaInterpreter::getGridMode);
lua_register(pGlobalLua, "addSpecialExit", TLuaInterpreter::addSpecialExit);
lua_register(pGlobalLua, "removeSpecialExit", TLuaInterpreter::removeSpecialExit);
lua_register(pGlobalLua, "getSpecialExits", TLuaInterpreter::getSpecialExits);
lua_register(pGlobalLua, "getSpecialExitsSwap", TLuaInterpreter::getSpecialExitsSwap);
lua_register(pGlobalLua, "clearSpecialExits", TLuaInterpreter::clearSpecialExits);
lua_register(pGlobalLua, "getRoomEnv", TLuaInterpreter::getRoomEnv);
lua_register(pGlobalLua, "getRoomUserData", TLuaInterpreter::getRoomUserData);
lua_register(pGlobalLua, "setRoomUserData", TLuaInterpreter::setRoomUserData);
lua_register(pGlobalLua, "searchRoomUserData", TLuaInterpreter::searchRoomUserData);
lua_register(pGlobalLua, "getRoomsByPosition", TLuaInterpreter::getRoomsByPosition);
lua_register(pGlobalLua, "getRoomsByPosition1", TLuaInterpreter::getRoomsByPosition1);
lua_register(pGlobalLua, "clearRoomUserData", TLuaInterpreter::clearRoomUserData);
lua_register(pGlobalLua, "clearRoomUserDataItem", TLuaInterpreter::clearRoomUserDataItem);
lua_register(pGlobalLua, "downloadFile", TLuaInterpreter::downloadFile);
lua_register(pGlobalLua, "appendCmdLine", TLuaInterpreter::appendCmdLine);
lua_register(pGlobalLua, "selectCmdLineText", TLuaInterpreter::selectCmdLineText);
lua_register(pGlobalLua, "getCmdLine", TLuaInterpreter::getCmdLine);
lua_register(pGlobalLua, "addCmdLineSuggestion", TLuaInterpreter::addCmdLineSuggestion);
lua_register(pGlobalLua, "removeCmdLineSuggestion", TLuaInterpreter::removeCmdLineSuggestion);
lua_register(pGlobalLua, "clearCmdLineSuggestions", TLuaInterpreter::clearCmdLineSuggestions);
lua_register(pGlobalLua, "addCmdLineBlacklist", TLuaInterpreter::addCmdLineBlacklist);
lua_register(pGlobalLua, "removeCmdLineBlacklist", TLuaInterpreter::removeCmdLineBlacklist);
lua_register(pGlobalLua, "clearCmdLineBlacklist", TLuaInterpreter::clearCmdLineBlacklist);
lua_register(pGlobalLua, "openUrl", TLuaInterpreter::openUrl);
lua_register(pGlobalLua, "sendSocket", TLuaInterpreter::sendSocket);
lua_register(pGlobalLua, "setRoomIDbyHash", TLuaInterpreter::setRoomIDbyHash);
lua_register(pGlobalLua, "getRoomIDbyHash", TLuaInterpreter::getRoomIDbyHash);
lua_register(pGlobalLua, "getRoomHashByID", TLuaInterpreter::getRoomHashByID);
lua_register(pGlobalLua, "addAreaName", TLuaInterpreter::addAreaName);
lua_register(pGlobalLua, "getRoomAreaName", TLuaInterpreter::getRoomAreaName);
lua_register(pGlobalLua, "deleteArea", TLuaInterpreter::deleteArea);
lua_register(pGlobalLua, "deleteRoom", TLuaInterpreter::deleteRoom);
lua_register(pGlobalLua, "setRoomChar", TLuaInterpreter::setRoomChar);
lua_register(pGlobalLua, "getRoomChar", TLuaInterpreter::getRoomChar);
lua_register(pGlobalLua, "setRoomCharColor", TLuaInterpreter::setRoomCharColor);
lua_register(pGlobalLua, "unsetRoomCharColor", TLuaInterpreter::unsetRoomCharColor);
lua_register(pGlobalLua, "getRoomCharColor", TLuaInterpreter::getRoomCharColor);
lua_register(pGlobalLua, "setRoomBorderColor", TLuaInterpreter::setRoomBorderColor);
lua_register(pGlobalLua, "getRoomBorderColor", TLuaInterpreter::getRoomBorderColor);
lua_register(pGlobalLua, "clearRoomBorderColor", TLuaInterpreter::clearRoomBorderColor);
lua_register(pGlobalLua, "setRoomBorderThickness", TLuaInterpreter::setRoomBorderThickness);
lua_register(pGlobalLua, "getRoomBorderThickness", TLuaInterpreter::getRoomBorderThickness);
lua_register(pGlobalLua, "clearRoomBorderThickness", TLuaInterpreter::clearRoomBorderThickness);
lua_register(pGlobalLua, "registerAnonymousEventHandler", TLuaInterpreter::registerAnonymousEventHandler);
lua_register(pGlobalLua, "saveMap", TLuaInterpreter::saveMap);
lua_register(pGlobalLua, "loadMap", TLuaInterpreter::loadMap);
lua_register(pGlobalLua, "setMainWindowSize", TLuaInterpreter::setMainWindowSize);
lua_register(pGlobalLua, "setAppStyleSheet", TLuaInterpreter::setAppStyleSheet);
lua_register(pGlobalLua, "setProfileStyleSheet", TLuaInterpreter::setProfileStyleSheet);
lua_register(pGlobalLua, "sendIrc", TLuaInterpreter::sendIrc);
lua_register(pGlobalLua, "openIRC", TLuaInterpreter::openIRC);
lua_register(pGlobalLua, "getIrcNick", TLuaInterpreter::getIrcNick);
lua_register(pGlobalLua, "getIrcServer", TLuaInterpreter::getIrcServer);
lua_register(pGlobalLua, "getIrcChannels", TLuaInterpreter::getIrcChannels);
lua_register(pGlobalLua, "getIrcConnectedHost", TLuaInterpreter::getIrcConnectedHost);
lua_register(pGlobalLua, "setIrcNick", TLuaInterpreter::setIrcNick);
lua_register(pGlobalLua, "setIrcServer", TLuaInterpreter::setIrcServer);
lua_register(pGlobalLua, "setIrcChannels", TLuaInterpreter::setIrcChannels);
lua_register(pGlobalLua, "restartIrc", TLuaInterpreter::restartIrc);
lua_register(pGlobalLua, "connectToServer", TLuaInterpreter::connectToServer);
lua_register(pGlobalLua, "getRooms", TLuaInterpreter::getRooms);
lua_register(pGlobalLua, "createMapLabel", TLuaInterpreter::createMapLabel);
lua_register(pGlobalLua, "deleteMapLabel", TLuaInterpreter::deleteMapLabel);
lua_register(pGlobalLua, "highlightRoom", TLuaInterpreter::highlightRoom);
lua_register(pGlobalLua, "unHighlightRoom", TLuaInterpreter::unHighlightRoom);
lua_register(pGlobalLua, "getMapLabels", TLuaInterpreter::getMapLabels);
lua_register(pGlobalLua, "getMapLabel", TLuaInterpreter::getMapLabel);
lua_register(pGlobalLua, "lockExit", TLuaInterpreter::lockExit);
lua_register(pGlobalLua, "hasExitLock", TLuaInterpreter::hasExitLock);
lua_register(pGlobalLua, "lockSpecialExit", TLuaInterpreter::lockSpecialExit);
lua_register(pGlobalLua, "hasSpecialExitLock", TLuaInterpreter::hasSpecialExitLock);
lua_register(pGlobalLua, "setExitStub", TLuaInterpreter::setExitStub);
lua_register(pGlobalLua, "connectExitStub", TLuaInterpreter::connectExitStub);
lua_register(pGlobalLua, "getExitStubs", TLuaInterpreter::getExitStubs);
lua_register(pGlobalLua, "getExitStubs1", TLuaInterpreter::getExitStubs1);
lua_register(pGlobalLua, "getExitStubsNames", TLuaInterpreter::getExitStubsNames);
lua_register(pGlobalLua, "setModulePriority", TLuaInterpreter::setModulePriority);
lua_register(pGlobalLua, "getModulePriority", TLuaInterpreter::getModulePriority);
lua_register(pGlobalLua, "updateMap", TLuaInterpreter::updateMap);
lua_register(pGlobalLua, "addMapEvent", TLuaInterpreter::addMapEvent);
lua_register(pGlobalLua, "removeMapEvent", TLuaInterpreter::removeMapEvent);
lua_register(pGlobalLua, "getMapEvents", TLuaInterpreter::getMapEvents);
lua_register(pGlobalLua, "addMapMenu", TLuaInterpreter::addMapMenu);
lua_register(pGlobalLua, "removeMapMenu", TLuaInterpreter::removeMapMenu);
lua_register(pGlobalLua, "getMapMenus", TLuaInterpreter::getMapMenus);
lua_register(pGlobalLua, "installPackage", TLuaInterpreter::installPackage);
lua_register(pGlobalLua, "installModule", TLuaInterpreter::installModule);
lua_register(pGlobalLua, "uninstallModule", TLuaInterpreter::uninstallModule);
lua_register(pGlobalLua, "reloadModule", TLuaInterpreter::reloadModule);
lua_register(pGlobalLua, "enableModuleSync", TLuaInterpreter::enableModuleSync);
lua_register(pGlobalLua, "disableModuleSync", TLuaInterpreter::disableModuleSync);
lua_register(pGlobalLua, "getModuleSync", TLuaInterpreter::getModuleSync);
lua_register(pGlobalLua, "getModules", TLuaInterpreter::getModules);
lua_register(pGlobalLua, "getPackages", TLuaInterpreter::getPackages);
lua_register(pGlobalLua, "getModuleInfo", TLuaInterpreter::getModuleInfo);
lua_register(pGlobalLua, "getPackageInfo", TLuaInterpreter::getPackageInfo);
lua_register(pGlobalLua, "setModuleInfo", TLuaInterpreter::setModuleInfo);
lua_register(pGlobalLua, "setPackageInfo", TLuaInterpreter::setPackageInfo);
lua_register(pGlobalLua, "createMapImageLabel", TLuaInterpreter::createMapImageLabel);
lua_register(pGlobalLua, "setMapZoom", TLuaInterpreter::setMapZoom);
lua_register(pGlobalLua, "getMapZoom", TLuaInterpreter::getMapZoom);
lua_register(pGlobalLua, "uninstallPackage", TLuaInterpreter::uninstallPackage);
lua_register(pGlobalLua, "setExitWeightFilter", TLuaInterpreter::setExitWeightFilter);
lua_register(pGlobalLua, "setExitWeight", TLuaInterpreter::setExitWeight);
lua_register(pGlobalLua, "setDoor", TLuaInterpreter::setDoor);
lua_register(pGlobalLua, "getDoors", TLuaInterpreter::getDoors);
lua_register(pGlobalLua, "getExitWeights", TLuaInterpreter::getExitWeights);
lua_register(pGlobalLua, "addSupportedTelnetOption", TLuaInterpreter::addSupportedTelnetOption);
lua_register(pGlobalLua, "setMergeTables", TLuaInterpreter::setMergeTables);
lua_register(pGlobalLua, "getModulePath", TLuaInterpreter::getModulePath);
lua_register(pGlobalLua, "getAreaExits", TLuaInterpreter::getAreaExits);
lua_register(pGlobalLua, "auditAreas", TLuaInterpreter::auditAreas);
lua_register(pGlobalLua, "sendMSDP", TLuaInterpreter::sendMSDP);
lua_register(pGlobalLua, "handleWindowResizeEvent", TLuaInterpreter::noop);
lua_register(pGlobalLua, "addCustomLine", TLuaInterpreter::addCustomLine);
lua_register(pGlobalLua, "removeCustomLine", TLuaInterpreter::removeCustomLine);
lua_register(pGlobalLua, "getCustomLines", TLuaInterpreter::getCustomLines);
lua_register(pGlobalLua, "getCustomLines1", TLuaInterpreter::getCustomLines1);
lua_register(pGlobalLua, "getMudletVersion", TLuaInterpreter::getMudletVersion);
lua_register(pGlobalLua, "openWebPage", TLuaInterpreter::openWebPage);
lua_register(pGlobalLua, "getAllRoomEntrances", TLuaInterpreter::getAllRoomEntrances);
lua_register(pGlobalLua, "getRoomUserDataKeys", TLuaInterpreter::getRoomUserDataKeys);
lua_register(pGlobalLua, "getAllRoomUserData", TLuaInterpreter::getAllRoomUserData);
lua_register(pGlobalLua, "searchAreaUserData", TLuaInterpreter::searchAreaUserData);
lua_register(pGlobalLua, "getMapUserData", TLuaInterpreter::getMapUserData);
lua_register(pGlobalLua, "getAreaUserData", TLuaInterpreter::getAreaUserData);
lua_register(pGlobalLua, "setMapUserData", TLuaInterpreter::setMapUserData);
lua_register(pGlobalLua, "setAreaUserData", TLuaInterpreter::setAreaUserData);
lua_register(pGlobalLua, "getAllAreaUserData", TLuaInterpreter::getAllAreaUserData);
lua_register(pGlobalLua, "getAllMapUserData", TLuaInterpreter::getAllMapUserData);
lua_register(pGlobalLua, "clearAreaUserData", TLuaInterpreter::clearAreaUserData);
lua_register(pGlobalLua, "clearAreaUserDataItem", TLuaInterpreter::clearAreaUserDataItem);
lua_register(pGlobalLua, "clearMapUserData", TLuaInterpreter::clearMapUserData);
lua_register(pGlobalLua, "clearMapUserDataItem", TLuaInterpreter::clearMapUserDataItem);
lua_register(pGlobalLua, "setDefaultAreaVisible", TLuaInterpreter::setDefaultAreaVisible);
lua_register(pGlobalLua, "getProfileName", TLuaInterpreter::getProfileName);
lua_register(pGlobalLua, "getCommandSeparator", TLuaInterpreter::getCommandSeparator);
lua_register(pGlobalLua, "raiseGlobalEvent", TLuaInterpreter::raiseGlobalEvent);
lua_register(pGlobalLua, "saveProfile", TLuaInterpreter::saveProfile);
#ifdef QT_TEXTTOSPEECH_LIB
lua_register(pGlobalLua, "ttsSpeak", TLuaInterpreter::ttsSpeak);
lua_register(pGlobalLua, "ttsSkip", TLuaInterpreter::ttsSkip);
lua_register(pGlobalLua, "ttsSetRate", TLuaInterpreter::ttsSetRate);
lua_register(pGlobalLua, "ttsSetPitch", TLuaInterpreter::ttsSetPitch);
lua_register(pGlobalLua, "ttsSetVolume", TLuaInterpreter::ttsSetVolume);
lua_register(pGlobalLua, "ttsGetRate", TLuaInterpreter::ttsGetRate);
lua_register(pGlobalLua, "ttsGetPitch", TLuaInterpreter::ttsGetPitch);
lua_register(pGlobalLua, "ttsGetVolume", TLuaInterpreter::ttsGetVolume);
lua_register(pGlobalLua, "ttsSetVoiceByName", TLuaInterpreter::ttsSetVoiceByName);
lua_register(pGlobalLua, "ttsSetVoiceByIndex", TLuaInterpreter::ttsSetVoiceByIndex);
lua_register(pGlobalLua, "ttsGetCurrentVoice", TLuaInterpreter::ttsGetCurrentVoice);
lua_register(pGlobalLua, "ttsGetVoices", TLuaInterpreter::ttsGetVoices);
lua_register(pGlobalLua, "ttsQueue", TLuaInterpreter::ttsQueue);
lua_register(pGlobalLua, "ttsGetQueue", TLuaInterpreter::ttsGetQueue);
lua_register(pGlobalLua, "ttsPause", TLuaInterpreter::ttsPause);
lua_register(pGlobalLua, "ttsResume", TLuaInterpreter::ttsResume);
lua_register(pGlobalLua, "ttsClearQueue", TLuaInterpreter::ttsClearQueue);
lua_register(pGlobalLua, "ttsGetCurrentLine", TLuaInterpreter::ttsGetCurrentLine);
lua_register(pGlobalLua, "ttsGetState", TLuaInterpreter::ttsGetState);
#endif // QT_TEXTTOSPEECH_LIB
lua_register(pGlobalLua, "setServerEncoding", TLuaInterpreter::setServerEncoding);
lua_register(pGlobalLua, "getServerEncoding", TLuaInterpreter::getServerEncoding);
lua_register(pGlobalLua, "getServerEncodingsList", TLuaInterpreter::getServerEncodingsList);
lua_register(pGlobalLua, "alert", TLuaInterpreter::alert);
lua_register(pGlobalLua, "tempPromptTrigger", TLuaInterpreter::tempPromptTrigger);
lua_register(pGlobalLua, "permPromptTrigger", TLuaInterpreter::permPromptTrigger);
lua_register(pGlobalLua, "getColumnCount", TLuaInterpreter::getColumnCount);
lua_register(pGlobalLua, "getRowCount", TLuaInterpreter::getRowCount);
lua_register(pGlobalLua, "getOS", TLuaInterpreter::getOS);
lua_register(pGlobalLua, "getProcessID", TLuaInterpreter::getProcessID);
lua_register(pGlobalLua, "getClipboardText", TLuaInterpreter::getClipboardText);
lua_register(pGlobalLua, "setClipboardText", TLuaInterpreter::setClipboardText);
lua_register(pGlobalLua, "getAvailableFonts", TLuaInterpreter::getAvailableFonts);
lua_register(pGlobalLua, "tempAnsiColorTrigger", TLuaInterpreter::tempAnsiColorTrigger);
lua_register(pGlobalLua, "setDiscordApplicationID", TLuaInterpreter::setDiscordApplicationID);
lua_register(pGlobalLua, "setDiscordGameUrl", TLuaInterpreter::setDiscordGameUrl);
lua_register(pGlobalLua, "usingMudletsDiscordID", TLuaInterpreter::usingMudletsDiscordID);
lua_register(pGlobalLua, "setDiscordState", TLuaInterpreter::setDiscordState);
lua_register(pGlobalLua, "setDiscordGame", TLuaInterpreter::setDiscordGame);
lua_register(pGlobalLua, "setDiscordDetail", TLuaInterpreter::setDiscordDetail);
lua_register(pGlobalLua, "setDiscordLargeIcon", TLuaInterpreter::setDiscordLargeIcon);
lua_register(pGlobalLua, "setDiscordLargeIconText", TLuaInterpreter::setDiscordLargeIconText);
lua_register(pGlobalLua, "setDiscordSmallIcon", TLuaInterpreter::setDiscordSmallIcon);
lua_register(pGlobalLua, "setDiscordSmallIconText", TLuaInterpreter::setDiscordSmallIconText);
lua_register(pGlobalLua, "getDiscordState", TLuaInterpreter::getDiscordState);
lua_register(pGlobalLua, "getDiscordDetail", TLuaInterpreter::getDiscordDetail);
lua_register(pGlobalLua, "getDiscordLargeIcon", TLuaInterpreter::getDiscordLargeIcon);
lua_register(pGlobalLua, "getDiscordLargeIconText", TLuaInterpreter::getDiscordLargeIconText);
lua_register(pGlobalLua, "getDiscordSmallIcon", TLuaInterpreter::getDiscordSmallIcon);
lua_register(pGlobalLua, "getDiscordSmallIconText", TLuaInterpreter::getDiscordSmallIconText);
lua_register(pGlobalLua, "setDiscordRemainingEndTime", TLuaInterpreter::setDiscordRemainingEndTime);
lua_register(pGlobalLua, "setDiscordElapsedStartTime", TLuaInterpreter::setDiscordElapsedStartTime);
lua_register(pGlobalLua, "getDiscordTimeStamps", TLuaInterpreter::getDiscordTimeStamps);
lua_register(pGlobalLua, "setDiscordParty", TLuaInterpreter::setDiscordParty);
lua_register(pGlobalLua, "getDiscordParty", TLuaInterpreter::getDiscordParty);
lua_register(pGlobalLua, "resetDiscordData", TLuaInterpreter::resetDiscordData);
lua_register(pGlobalLua, "getPlayerRoom", TLuaInterpreter::getPlayerRoom);
lua_register(pGlobalLua, "getSelection", TLuaInterpreter::getSelection);
lua_register(pGlobalLua, "getMapSelection", TLuaInterpreter::getMapSelection);
lua_register(pGlobalLua, "enableClickthrough", TLuaInterpreter::enableClickthrough);
lua_register(pGlobalLua, "disableClickthrough", TLuaInterpreter::disableClickthrough);
lua_register(pGlobalLua, "setLinkStyle", TLuaInterpreter::setLinkStyle);
lua_register(pGlobalLua, "resetLinkStyle", TLuaInterpreter::resetLinkStyle);
lua_register(pGlobalLua, "clearVisitedLinks", TLuaInterpreter::clearVisitedLinks);
lua_register(pGlobalLua, "addWordToDictionary", TLuaInterpreter::addWordToDictionary);
lua_register(pGlobalLua, "removeWordFromDictionary", TLuaInterpreter::removeWordFromDictionary);
lua_register(pGlobalLua, "spellCheckWord", TLuaInterpreter::spellCheckWord);
lua_register(pGlobalLua, "spellSuggestWord", TLuaInterpreter::spellSuggestWord);
lua_register(pGlobalLua, "getDictionaryWordList", TLuaInterpreter::getDictionaryWordList);
lua_register(pGlobalLua, "getTextFormat", TLuaInterpreter::getTextFormat);
lua_register(pGlobalLua, "getCharacterName", TLuaInterpreter::getCharacterName);
lua_register(pGlobalLua, "getProfileInformation", TLuaInterpreter::getProfileInformation);
lua_register(pGlobalLua, "setProfileInformation", TLuaInterpreter::setProfileInformation);
lua_register(pGlobalLua, "clearProfileInformation", TLuaInterpreter::clearProfileInformation);
lua_register(pGlobalLua, "getWindowsCodepage", TLuaInterpreter::getWindowsCodepage);
lua_register(pGlobalLua, "getHTTP", TLuaInterpreter::getHTTP);
lua_register(pGlobalLua, "customHTTP", TLuaInterpreter::customHTTP);
lua_register(pGlobalLua, "putHTTP", TLuaInterpreter::putHTTP);
lua_register(pGlobalLua, "postHTTP", TLuaInterpreter::postHTTP);
lua_register(pGlobalLua, "deleteHTTP", TLuaInterpreter::deleteHTTP);
lua_register(pGlobalLua, "getConnectionInfo", TLuaInterpreter::getConnectionInfo);
lua_register(pGlobalLua, "unzipAsync", TLuaInterpreter::unzipAsync);
lua_register(pGlobalLua, "setMapWindowTitle", TLuaInterpreter::setMapWindowTitle);
lua_register(pGlobalLua, "getMapWindowTitle", TLuaInterpreter::getMapWindowTitle);
lua_register(pGlobalLua, "getMudletInfo", TLuaInterpreter::getMudletInfo);
lua_register(pGlobalLua, "getMapBackgroundColor", TLuaInterpreter::getMapBackgroundColor);
lua_register(pGlobalLua, "setMapBackgroundColor", TLuaInterpreter::setMapBackgroundColor);
lua_register(pGlobalLua, "getMapRoomExitsColor", TLuaInterpreter::getMapRoomExitsColor);
lua_register(pGlobalLua, "setMapRoomExitsColor", TLuaInterpreter::setMapRoomExitsColor);
lua_register(pGlobalLua, "showNotification", TLuaInterpreter::showNotification);
lua_register(pGlobalLua, "saveJsonMap", TLuaInterpreter::saveJsonMap);
lua_register(pGlobalLua, "loadJsonMap", TLuaInterpreter::loadJsonMap);
lua_register(pGlobalLua, "registerMapInfo", TLuaInterpreter::registerMapInfo);
lua_register(pGlobalLua, "killMapInfo", TLuaInterpreter::killMapInfo);
lua_register(pGlobalLua, "enableMapInfo", TLuaInterpreter::enableMapInfo);
lua_register(pGlobalLua, "disableMapInfo", TLuaInterpreter::disableMapInfo);
lua_register(pGlobalLua, "getMapInfo", TLuaInterpreter::getMapInfo);
lua_register(pGlobalLua, "getProfileTabNumber", TLuaInterpreter::getProfileTabNumber);
lua_register(pGlobalLua, "addFileWatch", TLuaInterpreter::addFileWatch);
lua_register(pGlobalLua, "removeFileWatch", TLuaInterpreter::removeFileWatch);
lua_register(pGlobalLua, "addMouseEvent", TLuaInterpreter::addMouseEvent);
lua_register(pGlobalLua, "removeMouseEvent", TLuaInterpreter::removeMouseEvent);
lua_register(pGlobalLua, "getMouseEvents", TLuaInterpreter::getMouseEvents);
lua_register(pGlobalLua, "setConfig", TLuaInterpreter::setConfig);
lua_register(pGlobalLua, "addCommandLineMenuEvent", TLuaInterpreter::addCommandLineMenuEvent);
lua_register(pGlobalLua, "removeCommandLineMenuEvent", TLuaInterpreter::removeCommandLineMenuEvent);
lua_register(pGlobalLua, "deleteMap", TLuaInterpreter::deleteMap);
lua_register(pGlobalLua, "windowType", TLuaInterpreter::windowType);
lua_register(pGlobalLua, "getProfileStats", TLuaInterpreter::getProfileStats);
lua_register(pGlobalLua, "getBackgroundColor", TLuaInterpreter::getBackgroundColor);
lua_register(pGlobalLua, "getLabelStyleSheet", TLuaInterpreter::getLabelStyleSheet);
lua_register(pGlobalLua, "getLabelSizeHint", TLuaInterpreter::getLabelSizeHint);
lua_register(pGlobalLua, "announce", TLuaInterpreter::announce);
lua_register(pGlobalLua, "scrollTo", TLuaInterpreter::scrollTo);
lua_register(pGlobalLua, "getScroll", TLuaInterpreter::getScroll);
lua_register(pGlobalLua, "getConfig", TLuaInterpreter::getConfig);
lua_register(pGlobalLua, "setSaveCommandHistory", TLuaInterpreter::setSaveCommandHistory);
lua_register(pGlobalLua, "getSaveCommandHistory", TLuaInterpreter::getSaveCommandHistory);
lua_register(pGlobalLua, "enableScrolling", TLuaInterpreter::enableScrolling);
lua_register(pGlobalLua, "disableScrolling", TLuaInterpreter::disableScrolling);
lua_register(pGlobalLua, "clearMapSelection", TLuaInterpreter::clearMapSelection);
lua_register(pGlobalLua, "scrollingActive", TLuaInterpreter::scrollingActive);
lua_register(pGlobalLua, "findItems", TLuaInterpreter::findItems);
lua_register(pGlobalLua, "holdingModifiers", TLuaInterpreter::holdingModifiers);
lua_register(pGlobalLua, "getProfiles", TLuaInterpreter::getProfiles);
lua_register(pGlobalLua, "loadProfile", TLuaInterpreter::loadProfile);
lua_register(pGlobalLua, "closeProfile", TLuaInterpreter::closeProfile);
lua_register(pGlobalLua, "getCollisionLocationsInArea", TLuaInterpreter::getCollisionLocationsInArea);
lua_register(pGlobalLua, "exportAreaImage", TLuaInterpreter::exportAreaImage);
lua_register(pGlobalLua, "disableTimeStamps", TLuaInterpreter::disableTimeStamps);
lua_register(pGlobalLua, "enableTimeStamps", TLuaInterpreter::enableTimeStamps);
lua_register(pGlobalLua, "timeStampsEnabled", TLuaInterpreter::timeStampsEnabled);
lua_register(pGlobalLua, "setActiveProfile", TLuaInterpreter::setActiveProfile);
lua_register(pGlobalLua, "getKeyCode", TLuaInterpreter::getKeyCode);
#ifdef MUDLET_MEMORY_TRACKING
lua_register(pGlobalLua, "getProcessMemoryUsage", TLuaInterpreter::getProcessMemoryUsage);
lua_register(pGlobalLua, "getSubsystemMemoryStats", TLuaInterpreter::getSubsystemMemoryStats);
#endif
// PLACEMARKER: End of main Lua interpreter functions registration
// check new functions against https://www.linguistic-antipatterns.com when creating them
// Create MMCP Chat Library
lua_newtable(pGlobalLua);
lua_pushcfunction(pGlobalLua, TLuaInterpreter::mmcpChatTo);
lua_setfield(pGlobalLua, -2, "chatTo");
lua_pushcfunction(pGlobalLua, TLuaInterpreter::mmcpChatAll);
lua_setfield(pGlobalLua, -2, "chatAll");
lua_pushcfunction(pGlobalLua, TLuaInterpreter::mmcpAllowSnoop);
lua_setfield(pGlobalLua, -2, "allowSnoop");
lua_pushcfunction(pGlobalLua, TLuaInterpreter::mmcpCall);
lua_setfield(pGlobalLua, -2, "call");
lua_pushcfunction(pGlobalLua, TLuaInterpreter::mmcpEmoteAll);
lua_setfield(pGlobalLua, -2, "emoteAll");
lua_pushcfunction(pGlobalLua, TLuaInterpreter::mmcpChatGroup);
lua_setfield(pGlobalLua, -2, "chatGroup");
lua_pushcfunction(pGlobalLua, TLuaInterpreter::mmcpIgnore);
lua_setfield(pGlobalLua, -2, "ignore");
lua_pushcfunction(pGlobalLua, TLuaInterpreter::mmcpDisplayClientList);
lua_setfield(pGlobalLua, -2, "displayClientList");
lua_pushcfunction(pGlobalLua, TLuaInterpreter::mmcpChatName);
lua_setfield(pGlobalLua, -2, "chatName");
lua_pushcfunction(pGlobalLua, TLuaInterpreter::mmcpPing);
lua_setfield(pGlobalLua, -2, "ping");
lua_pushcfunction(pGlobalLua, TLuaInterpreter::mmcpPrivate);
lua_setfield(pGlobalLua, -2, "setPrivate");
lua_pushcfunction(pGlobalLua, TLuaInterpreter::mmcpServe);
lua_setfield(pGlobalLua, -2, "serve");
lua_pushcfunction(pGlobalLua, TLuaInterpreter::mmcpSetGroup);
lua_setfield(pGlobalLua, -2, "setGroup");
lua_pushcfunction(pGlobalLua, TLuaInterpreter::mmcpSendSideChannel);
lua_setfield(pGlobalLua, -2, "sendSideChannel");
lua_pushcfunction(pGlobalLua, TLuaInterpreter::mmcpSnoop);
lua_setfield(pGlobalLua, -2, "snoop");
// Tagging for possible 4.21.1 inclusion
/*
lua_pushcfunction(pGlobalLua, TLuaInterpreter::mmcpAccept);
lua_setfield(pGlobalLua, -2, "accept");
lua_pushcfunction(pGlobalLua, TLuaInterpreter::mmcpDeny);
lua_setfield(pGlobalLua, -2, "deny");
lua_pushcfunction(pGlobalLua, TLuaInterpreter::mmcpDoNotDisturb);
lua_setfield(pGlobalLua, -2, "setDoNotDisturb");
lua_pushcfunction(pGlobalLua, TLuaInterpreter::mmcpStartServer);
lua_setfield(pGlobalLua, -2, "startServer");
lua_pushcfunction(pGlobalLua, TLuaInterpreter::mmcpStopServer);
lua_setfield(pGlobalLua, -2, "stopServer");
lua_pushcfunction(pGlobalLua, TLuaInterpreter::mmcpRequestConnections);
lua_setfield(pGlobalLua, -2, "request");
lua_pushcfunction(pGlobalLua, TLuaInterpreter::mmcpPeekConnections);
lua_setfield(pGlobalLua, -2, "peek");
*/
lua_pushcfunction(pGlobalLua, TLuaInterpreter::mmcpDisconnect);
lua_setfield(pGlobalLua, -2, "disconnect");
lua_pushcfunction(pGlobalLua, TLuaInterpreter::mmcpGetClientFlags);
lua_setfield(pGlobalLua, -2, "getClientFlags");
lua_pushcfunction(pGlobalLua, TLuaInterpreter::mmcpGetClientList);
lua_setfield(pGlobalLua, -2, "getClientList");
lua_setglobal(pGlobalLua, "mmcp");
QStringList additionalLuaPaths;
QStringList additionalCPaths;
const auto appPath{QCoreApplication::applicationDirPath()};
const auto profilePath{mudlet::getMudletPath(enums::profileHomePath, hostName)};
// Allow for modules or libraries placed in the profile root directory:
additionalLuaPaths << qsl("%1/?.lua").arg(profilePath);
additionalLuaPaths << qsl("%1/?/init.lua").arg(profilePath);
#if defined(Q_OS_WINDOWS)
additionalCPaths << qsl("%1/?.dll").arg(profilePath);
#else
additionalCPaths << qsl("%1/?.so").arg(profilePath);
#endif
#if defined(Q_OS_LINUX)
// AppInstaller on Linux would like the C search path to also be set to
// a ./lib sub-directory of the current binary directory:
additionalCPaths << qsl("%1/lib/?.so").arg(appPath);
#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);
#elif defined(Q_OS_MACOS)
// macOS app bundle would like the search path to also be set to the current
// binary directory for both modules and binary libraries:
additionalCPaths << qsl("%1/?.so").arg(appPath);
additionalLuaPaths << qsl("%1/?.lua").arg(appPath);
// Luarocks installs rocks locally for developers, even with sudo
additionalCPaths << qsl("%1/.luarocks/lib/lua/5.1/?.so").arg(QStandardPaths::standardLocations(QStandardPaths::HomeLocation).first());
additionalLuaPaths << qsl("%1/.luarocks/share/lua/5.1/?.lua;%1/.luarocks/share/lua/5.1/?/init.lua").arg(QStandardPaths::standardLocations(QStandardPaths::HomeLocation).first());
#endif
insertNativeSeparatorsFunction(pGlobalLua);
luaL_dostring(pGlobalLua, qsl("package.cpath = toNativeSeparators([[%1;]]) .. package.cpath").arg(additionalCPaths.join(QLatin1Char(';'))).toUtf8().constData());
luaL_dostring(pGlobalLua, qsl("package.path = toNativeSeparators([[%1;]]) .. package.path").arg(additionalLuaPaths.join(QLatin1Char(';'))).toUtf8().constData());
/*
* For uses like this where we try more than one alternative, only include
* the consequence message for the LAST one (it doesn't make sense to show
* it in multiple messages about the same functional library with alternative
* names):
* Reminder for parameters to loadLuaModule:
* 1 - Store for the result messaged (required!)
* 2 - string to pass to the Lua requires(...) function (required, will be
* different for variants of the same module/library)
* 3 - string explaining what gets broken if (one of the alternatives of)
* the module/library is not loaded (optional, may be a null/empty
* QString if needed for a placeholder for following arguments)
* 4 - string describing the module (optional, for alternatives may be
* different and reflect the luarock name for the particular
* alternative, when omitted or a null/empty QString the second
* argument is used instead)
* 5 - string used in Lua as the identifier for the loaded module/library,
* passed as X in the lua 'X = require("name")' (optional, in which
* case nothing is put before the "require" in that usage and the module
* assumes whatever Lua name it offers).
*/
QQueue<QString> modLoadMessageQueue;
loadLuaModule(modLoadMessageQueue, QLatin1String("lfs"), tr("Probably will not be able to access Mudlet Lua code."), QLatin1String("lfs (Lua File System)"));
while (!modLoadMessageQueue.isEmpty()) {
mpHost->postMessage(modLoadMessageQueue.dequeue());
}
bool loaded = loadLuaModule(modLoadMessageQueue, QLatin1String("brimworks.zip"), QString(), qsl("lua-zip"), qsl("zip"));
if (!loaded) {
loadLuaModule(modLoadMessageQueue, QLatin1String("zip"), QString(), qsl("luazip"));
}
while (!modLoadMessageQueue.isEmpty()) {
mpHost->postMessage(modLoadMessageQueue.dequeue());
}
loaded = loadLuaModule(modLoadMessageQueue, QLatin1String("rex_pcre2"), QString(), QString(), qsl("rex_pcre"));
if (loaded) {
// PCRE2 renamed fullinfo() to patterninfo(), add wrapper for backwards compatibility
luaL_dostring(pGlobalLua, "rex_pcre.fullinfo = rex_pcre.patterninfo");
} else {
loadLuaModule(modLoadMessageQueue, QLatin1String("rex_pcre"), tr("Some regular expression functions may not be available."));
}
while (!modLoadMessageQueue.isEmpty()) {
mpHost->postMessage(modLoadMessageQueue.dequeue());
}
loadLuaModule(modLoadMessageQueue, QLatin1String("luasql.sqlite3"), tr("Database support will not be available."), QLatin1String("sqlite3"), QLatin1String("luasql"));
while (!modLoadMessageQueue.isEmpty()) {
mpHost->postMessage(modLoadMessageQueue.dequeue());
}
loaded = loadLuaModule(modLoadMessageQueue, QLatin1String("lua-utf8"), QString(), QLatin1String("lua-utf8"), QLatin1String("utf8"));
if (!loaded) {
loadLuaModule(modLoadMessageQueue, QLatin1String("utf8"), tr("utf8.* Lua functions won't be available."));
}
while (!modLoadMessageQueue.isEmpty()) {
mpHost->postMessage(modLoadMessageQueue.dequeue());
}
loadLuaModule(modLoadMessageQueue, QLatin1String("yajl"), tr("yajl.* Lua functions won't be available."), QString(), QLatin1String("yajl"));
while (!modLoadMessageQueue.isEmpty()) {
mpHost->postMessage(modLoadMessageQueue.dequeue());
}
loadLuaModule(modLoadMessageQueue, QLatin1String("lpeg"), tr("lpeg.* Lua functions won't be available."), QString(), QLatin1String("lpeg"));
while (!modLoadMessageQueue.isEmpty()) {
mpHost->postMessage(modLoadMessageQueue.dequeue());
}
QString tn = "atcp";
QStringList args;
set_lua_table(tn, args);
tn = "channel102";
set_lua_table(tn, args);
lua_pop(pGlobalLua, lua_gettop(pGlobalLua));
//FIXME make function call in destructor lua_close(L);
}
// No documentation available in wiki - internal function
lua_State* TLuaInterpreter::getLuaGlobalState()
{
return pGlobalLua;
}
// No documentation available in wiki - internal function
void TLuaInterpreter::setupLanguageData()
{
// Creates a 'mudlet.translations' table with directions
lua_State* L = pGlobalLua;
// 'mudlet' global table
lua_createtable(L, 0, 1);
// language-specific table
lua_createtable(L, 0, 1);
// language-specific directions table
lua_createtable(L, 0, 24);
// this cannot be generated programmatically since lupdate needs the explicit string at extraction time
lua_pushstring(L, QCoreApplication::translate("directions", "north", "Entering this direction will move the player in the game").toUtf8().constData());
lua_setfield(L, -2, "north");
lua_pushstring(L, QCoreApplication::translate("directions", "n", "Entering this direction will move the player in the game").toUtf8().constData());
lua_setfield(L, -2, "n");
lua_pushstring(L, QCoreApplication::translate("directions", "east", "Entering this direction will move the player in the game").toUtf8().constData());
lua_setfield(L, -2, "east");
lua_pushstring(L, QCoreApplication::translate("directions", "e", "Entering this direction will move the player in the game").toUtf8().constData());
lua_setfield(L, -2, "s");
lua_pushstring(L, QCoreApplication::translate("directions", "south", "Entering this direction will move the player in the game").toUtf8().constData());
lua_setfield(L, -2, "south");
lua_pushstring(L, QCoreApplication::translate("directions", "s", "Entering this direction will move the player in the game").toUtf8().constData());
lua_setfield(L, -2, "s");
lua_pushstring(L, QCoreApplication::translate("directions", "west", "Entering this direction will move the player in the game").toUtf8().constData());
lua_setfield(L, -2, "west");
lua_pushstring(L, QCoreApplication::translate("directions", "w", "Entering this direction will move the player in the game").toUtf8().constData());
lua_setfield(L, -2, "w");
lua_pushstring(L, QCoreApplication::translate("directions", "northeast", "Entering this direction will move the player in the game").toUtf8().constData());
lua_setfield(L, -2, "northeast");
lua_pushstring(L, QCoreApplication::translate("directions", "ne", "Entering this direction will move the player in the game").toUtf8().constData());
lua_setfield(L, -2, "ne");
lua_pushstring(L, QCoreApplication::translate("directions", "southeast", "Entering this direction will move the player in the game").toUtf8().constData());
lua_setfield(L, -2, "southeast");
lua_pushstring(L, QCoreApplication::translate("directions", "se", "Entering this direction will move the player in the game").toUtf8().constData());
lua_setfield(L, -2, "se");
lua_pushstring(L, QCoreApplication::translate("directions", "southwest", "Entering this direction will move the player in the game").toUtf8().constData());
lua_setfield(L, -2, "southwest");
lua_pushstring(L, QCoreApplication::translate("directions", "sw", "Entering this direction will move the player in the game").toUtf8().constData());
lua_setfield(L, -2, "sw");
lua_pushstring(L, QCoreApplication::translate("directions", "northwest", "Entering this direction will move the player in the game").toUtf8().constData());
lua_setfield(L, -2, "northwest");
lua_pushstring(L, QCoreApplication::translate("directions", "nw", "Entering this direction will move the player in the game").toUtf8().constData());
lua_setfield(L, -2, "nw");
lua_pushstring(L, QCoreApplication::translate("directions", "in", "Entering this direction will move the player in the game").toUtf8().constData());
lua_setfield(L, -2, "in");
lua_pushstring(L, QCoreApplication::translate("directions", "i", "Entering this direction will move the player in the game").toUtf8().constData());
lua_setfield(L, -2, "i");
lua_pushstring(L, QCoreApplication::translate("directions", "out", "Entering this direction will move the player in the game").toUtf8().constData());
lua_setfield(L, -2, "out");
lua_pushstring(L, QCoreApplication::translate("directions", "o", "Entering this direction will move the player in the game").toUtf8().constData());
lua_setfield(L, -2, "o");
lua_pushstring(L, QCoreApplication::translate("directions", "up", "Entering this direction will move the player in the game").toUtf8().constData());
lua_setfield(L, -2, "up");
lua_pushstring(L, QCoreApplication::translate("directions", "u", "Entering this direction will move the player in the game").toUtf8().constData());
lua_setfield(L, -2, "u");
lua_pushstring(L, QCoreApplication::translate("directions", "down", "Entering this direction will move the player in the game").toUtf8().constData());
lua_setfield(L, -2, "down");
lua_pushstring(L, QCoreApplication::translate("directions", "d", "Entering this direction will move the player in the game").toUtf8().constData());
lua_setfield(L, -2, "d");
// finalize language-specific directions table
lua_setfield(L, -2, mudlet::self()->getInterfaceLanguage().toUtf8().constData());
lua_pushstring(L, mudlet::self()->getInterfaceLanguage().toUtf8().constData());
lua_setfield(L, -2, "interfacelanguage");
lua_setfield(L, -2, "translations");
lua_setglobal(L, "mudlet");
lua_pop(L, lua_gettop(L));
}
// No documentation available in wiki - internal function
void TLuaInterpreter::initIndenterGlobals()
{
Q_ASSERT_X(!pIndenterState, "TLuaInterpreter::initIndenterGlobals()", "Indenter state is already initialized - re-initializing it is very expensive!");
// Initialise a slimmed-down Lua state just to run the indenter in a separate sandbox.
// The indenter by default pollutes the global environment with some utility functions
// and we don't want to tie ourselves to it by exposing them for scripting.
pIndenterState.reset(newstate());
storeHostInLua(pIndenterState.get(), mpHost);
luaL_openlibs(pIndenterState.get());
lua_pushstring(pIndenterState.get(), "SESSION");
lua_pushnumber(pIndenterState.get(), mHostID);
lua_settable(pIndenterState.get(), LUA_GLOBALSINDEX);
lua_pushstring(pIndenterState.get(), "SCRIPT_NAME");
lua_pushstring(pIndenterState.get(), "Lua Indenter Interpreter");
lua_settable(pIndenterState.get(), LUA_GLOBALSINDEX);
lua_pushstring(pIndenterState.get(), "SCRIPT_ID");
lua_pushnumber(pIndenterState.get(), -2); // ID 2 is used to indicate that this is the indenter Lua interpreter
lua_settable(pIndenterState.get(), LUA_GLOBALSINDEX);
lua_register(pIndenterState.get(), "echo", TLuaInterpreter::echo);
lua_register(pIndenterState.get(), "tempTimer", TLuaInterpreter::tempTimer);
lua_register(pIndenterState.get(), "send", TLuaInterpreter::sendRaw);
lua_register(pIndenterState.get(), "debugc", TLuaInterpreter::debug);
// PLACEMARKER: End of indenter Lua interpreter functions registration
/*
* Additional paths for the lua/C package search paths - '?' (and any '.'s,
* in the file name, apart from the one before the filename extension) are
* handled specially! See: https://stackoverflow.com/q/31904392/4805858 :
*/
QStringList additionalLuaPaths;
QStringList additionalCPaths;
const QString appPath{QCoreApplication::applicationDirPath()};
#if defined(Q_OS_MACOS)
// macOS app bundle would like the search path for the binary modules to
// also be set to the current binary directory:
additionalCPaths << qsl("%1/?.so").arg(appPath);
#elif defined(Q_OS_LINUX)
// AppInstaller on Linux would like the search path for the binary modules
// to also be set to a lib sub-directory of the application directory:
additionalCPaths << qsl("%1/lib/?.so").arg(appPath);
#endif
insertNativeSeparatorsFunction(pIndenterState.get());
// 1 installed *nix case - probably not applicable to Windows
// "LUA_DEFAULT_PATH/?.lua" (if defined and not empty)
if (!qsl(LUA_DEFAULT_PATH).isEmpty()) {
additionalLuaPaths << qsl(LUA_DEFAULT_PATH "/?.lua");
}
// 2 AppImage (directory of executable) - not needed for Wndows:
// "<applicationDirectory>/?.lua"
#if !defined(Q_OS_WINDOWS)
additionalLuaPaths << qsl("%1/?.lua").arg(appPath);
#endif
// 3 QMake shadow builds without CONFIG containing "debug_and_release" but
// with "debug_and_release_target" (default on most OS but NOT Windows):
// "<applicationDirectory>/../3rdparty/?.lua"
additionalLuaPaths << qsl("%1/../3rdparty/?.lua").arg(appPath);
// 4 QMake shadow builds with CONFIG containing "debug_and_release" AND
// "debug_and_release_target" (usually Windows):
// "<applicationDirectory>/../../3rdparty/?.lua"
additionalLuaPaths << qsl("%1/../../3rdparty/?.lua").arg(appPath);
// 5 CMake shadow builds
// "<applicationDirectory>/../../mudlet/3rdparty/?.lua"
additionalLuaPaths << qsl("%1/../../mudlet/3rdparty/?.lua").arg(appPath);
int error = luaL_dostring(pIndenterState.get(), qsl("package.path = toNativeSeparators([[%1;]] .. package.path)").arg(additionalLuaPaths.join(QLatin1Char(';'))).toUtf8().constData());
if (!error && !additionalCPaths.isEmpty()) {
error = luaL_dostring(pIndenterState.get(), qsl("package.cpath = toNativeSeparators([[%1;]] .. package.cpath)").arg(additionalCPaths.join(QLatin1Char(';'))).toUtf8().constData());
}
// clang-format off
if (!error) {
error = luaL_dostring(pIndenterState.get(), R"LUA(
require('lcf.workshop.base')
get_ast = request('!.lua.code.get_ast')
get_formatted_code = request('!.lua.code.ast_as_code')
)LUA");
// clang-format on
}
if (error) {
QString e = tr("No error message available from Lua.");
if (lua_isstring(pIndenterState.get(), -1)) {
e = tr("Lua error: %1.").arg(lua_tostring(pIndenterState.get(), -1));
}
QString msg = qsl("%1\n").arg(tr("[ ERROR ] - Cannot load code formatter, indenting functionality won't be available."));
msg.append(e);
mpHost->postMessage(msg);
}
lua_pop(pIndenterState.get(), lua_gettop(pIndenterState.get()));
}
// No documentation available in wiki - internal function called AFTER
// initLuaGlobals() {it depends on that to load up some Lua libraries, including
// the LFS "Lua File System" one first}:
void TLuaInterpreter::loadGlobal()
{
#if defined(Q_OS_WINDOWS)
loadUtf8Filenames();
#endif
setupLanguageData();
const QString executablePath{QCoreApplication::applicationDirPath()};
// Initialise the list of path and file names so that
// getMudletLuaDefaultPaths() can report them later if asked:
mPossiblePaths = QStringList{
#if defined(Q_OS_MACOS)
// Load relatively to MacOS inside Resources when we're in a .app
// bundle, as mudlet-lua always gets copied in by the build script into
// the bundle for the Mac installer build:
qsl("%1/../Resources/mudlet-lua/lua/LuaGlobal.lua").arg(executablePath),
#endif
// For the installer we put the lua files under the executable's
// location. This is the case for the Windows install:
QDir::toNativeSeparators(qsl("%1/mudlet-lua/lua/LuaGlobal.lua").arg(executablePath)),
// Although a no-op for an in source build an additional "../src/"
// allows location of lua code when object code is in a directory
// alongside the src directory as occurs using Qt Creator "Shadow
// Builds":
QDir::toNativeSeparators(qsl("%1/../src/mudlet-lua/lua/LuaGlobal.lua").arg(executablePath)),
// Windows builds (or others where the qmake project file has CONFIG
// containing debug_and_release AND debug_and_release_target options)
// may be an additional sub-directory down:
QDir::toNativeSeparators(qsl("%1/../../src/mudlet-lua/lua/LuaGlobal.lua").arg(executablePath)),
// CMake builds done from Qt Creator tend to make their build directory
// be in a "out-of-source" (the more common name for what Qt calls
// "Shadow Builds") on the same level as the top level CMakeList.txt
// project file - which is one level up compared to the QMake case.
// and in a "src" subdirectory (to match the relative source file
// location to that top-level project file) of the main project
// "mudlet" directory:
QDir::toNativeSeparators(qsl("%1/../../../src/mudlet-lua/lua/LuaGlobal.lua").arg(executablePath)),
// CMake builds from Qt Creator on Windows 11 appear to use this directory:
QDir::toNativeSeparators(qsl("%1/../../../mudlet-lua/lua/LuaGlobal.lua").arg(executablePath))};
// Although it is relatively easy to detect whether something is #define d
// it is not so easy to detect what it contains at the preprocessor stage,
// so leave checking for its contents to run-time - this one is the one
// for Linux/FreeBSD where the read-only shared Lua files go into the
// /usr/share part of the file-system:
if (!qsl(LUA_DEFAULT_PATH).isEmpty()) {
mPossiblePaths << QDir::toNativeSeparators(qsl(LUA_DEFAULT_PATH "/LuaGlobal.lua"));
}
// The build-time source path lets development builds and test binaries
// find LuaGlobal.lua regardless of where the binary runs from.
// Only add if the path actually exists to avoid leaking build-time paths
// in error messages on packaged end-user builds:
if (!qsl(LUA_SOURCE_PATH).isEmpty()) {
const auto sourcePath = QDir::toNativeSeparators(qsl(LUA_SOURCE_PATH "/LuaGlobal.lua"));
if (QFileInfo::exists(sourcePath)) {
mPossiblePaths << sourcePath;
}
}
QStringList failedMessages{};
// uncomment the following to enable some debugging texts in the LuaGlobal.lua script:
// luaL_dostring(pGlobalLua, qsl("debugLoading = true").toUtf8().constData());
#if defined(Q_OS_WINDOWS)
#if QT_VERSION < QT_VERSION_CHECK(6, 6, 0)
// Needed to enable permissions checks on NTFS file systems - normally
// turned off for performance reasons:
extern Q_CORE_EXPORT int qt_ntfs_permission_lookup;
#endif
#endif
int error;
for (const auto& pathFileName : std::as_const(mPossiblePaths)) {
if (!(QFileInfo::exists(pathFileName))) {
failedMessages << tr("%1 (doesn't exist)", "This file doesn't exist").arg(pathFileName);
continue;
}
if (!(QFileInfo(pathFileName).isFile())) {
failedMessages << tr("%1 (isn't a file or symlink to a file)").arg(pathFileName);
continue;
}
#if defined(Q_OS_WINDOWS)
// Turn on permission checking on NTFS file systems
#if QT_VERSION < QT_VERSION_CHECK(6, 6, 0)
qt_ntfs_permission_lookup++;
#else
qEnableNtfsPermissionChecks();
#endif
#endif
if (!(QFileInfo(pathFileName).isReadable())) {
failedMessages << tr("%1 (isn't a readable file or symlink to a readable file)").arg(pathFileName);
continue;
}
#if defined(Q_OS_WINDOWS)
// Turn off permission checking on NTFS file systems
#if QT_VERSION < QT_VERSION_CHECK(6, 6, 0)
qt_ntfs_permission_lookup--;
#else
qDisableNtfsPermissionChecks();
#endif
#endif
// Leave a global variable set to the path so we can use it to find the
// other files around it - the script will convert the directory
// separators as necessary:
Q_ASSERT_X(!pathFileName.isEmpty(), "TLuaInterpreter::loadGlobal()", "trying to call QFileInfo(path).absolutePath() when path is empty");
luaL_dostring(pGlobalLua, qsl("luaGlobalPath = \"%1\"").arg(QFileInfo(pathFileName).absolutePath()).toUtf8().constData());
// load via Qt so UTF8 paths work on Windows - Lua can't handle it
auto luaGlobal = readScriptFile(pathFileName);
if (luaGlobal.isEmpty()) {
failedMessages << tr("%1 (couldn't read file)", "This file could not be read for some reason (for example, no permission)").arg(pathFileName);
continue;
}
error = luaL_dostring(pGlobalLua, luaGlobal.toUtf8().constData());
if (!error) {
return;
}
qWarning() << "TLuaInterpreter::loadGlobal() loading " << pathFileName << " failed: " << lua_tostring(pGlobalLua, -1);
failedMessages << qsl("%1 (%2)").arg(pathFileName, lua_tostring(pGlobalLua, -1));
}
mpHost->postMessage(tr("[ ERROR ] - Couldn't find, load and successfully run LuaGlobal.lua - your Mudlet is broken!\nTried these locations:\n%1").arg(failedMessages.join(QChar::LineFeed)));
}
// No documentation available in wiki - internal function
// Returns contents of the file or empty string if it couldn't be read
QString TLuaInterpreter::readScriptFile(const QString& path) const
{
QFile file(path);
if (!file.open(QFile::ReadOnly | QFile::Text)) {
return QString();
}
QTextStream in(&file);
/*
* FIXME: Qt Documentation for this method reports:
* "Reads the entire content of the stream, and returns it as a QString.
* Avoid this function when working on large files, as it will consume a
* significant amount of memory.
*
* Calling readLine() is better if you do not know how much data is
* available."
*/
QString text = in.readAll();
file.close();
return text;
}
#if defined(Q_OS_WINDOWS)
// No documentation available in wiki - internal function
// loads utf8_filenames from the resource system directly so it is not affected by
// non-ASCII characters that might be present in the users filesystem
void TLuaInterpreter::loadUtf8Filenames()
{
auto path = qsl(":/mudlet-lua/lua/utf8_filenames.lua");
auto text = readScriptFile(path);
if (text.isEmpty()) {
qWarning() << "TLuaInterpreter::loadUtf8Filenames() ERROR: couldn't read file: " << path;
return;
}
if (mpHost->getLuaInterpreter()->compileAndExecuteScript(text)) {
qDebug() << "TLuaInterpreter::loadUtf8Filenames() - patched Lua IO functions to work on Windows with UTF8";
} else {
qWarning() << "TLuaInterpreter::loadUtf8Filenames() ERROR: there was an error running the script";
}
}
#endif
// No documentation available in wiki - internal function
std::pair<int, QString> TLuaInterpreter::createPermScript(const QString& name, const QString& parent, const QString& luaCode)
{
TScript* pS;
if (parent.isEmpty()) {
pS = new TScript(qsl("newPermScriptWithoutAnId"), mpHost);
} else {
// FIXME: There can be more than one script with the same name - we will
// use only the FIRST one for now, but we really ought to enhance the
// API to handle more than one potential parent with the same name:
auto ids = mpHost->getScriptUnit()->findItems(parent);
if (ids.empty()) {
return {-1, qsl("parent '%1' not found").arg(parent)}; //parent not found
}
auto pParentScript = mpHost->getScriptUnit()->getScript(ids.at(0));
pS = new TScript(pParentScript, mpHost);
}
pS->setIsFolder((luaCode.isEmpty()));
pS->setName(name);
// This will lead to the generation of the ID number:
mpHost->getScriptUnit()->registerScript(pS);
if (!pS->setScript(luaCode)) {
const QString errMsg = pS->getError();
delete pS;
return {-1, qsl("unable to compile \"%1\", reason: %2").arg(luaCode, errMsg)};
}
const int id = pS->getID();
pS->setIsActive(false);
updateEditor();
return {id, QString()};
}
// No documentation available in wiki - internal function
// pos is 0 for the first script with the matching name so needs
// to be incremented if it is to be referred to in an error message, but can
// be used to directly index into the QVector<int> that is "ids".
std::pair<int, QString> TLuaInterpreter::setScriptCode(const QString& name, const QString& luaCode, const int pos)
{
if (name.isEmpty()) {
return {-1, qsl("cannot have an empty string as name")};
}
const auto ids = mpHost->getScriptUnit()->findItems(name);
int id = -1;
TScript* pS = nullptr;
if (pos >= 0 && pos < static_cast<ptrdiff_t>(ids.size())) {
id = ids.at(pos);
pS = mpHost->getScriptUnit()->getScript(id);
}
if (!pS) {
return {-1, qsl("script \"%1\" at position %2 not found").arg(name, QString::number(pos + 1))}; //script not found
}
const auto oldCode = pS->getScript();
if (!pS->setScript(luaCode)) {
const QString errMsg = pS->getError();
pS->setScript(oldCode);
return {-1, qsl("unable to compile \"%1\" for the script \"%2\" at position %3, reason: %4").arg(luaCode, name, QString::number(pos + 1), errMsg)};
}
if (mpHost->mpEditorDialog) {
mpHost->mpEditorDialog->writeScript(id);
}
return {id, QString()};
}
// No documentation available in wiki - internal function
std::pair<int, QString> TLuaInterpreter::startPermTimer(const QString& name, const QString& parent, double timeout, const QString& function)
{
const QTime time = QTime(0, 0, 0, 0).addMSecs(qRound(timeout * 1000));
TTimer* pT;
if (parent.isEmpty()) {
pT = new TTimer(qsl("newPermTimerWithoutAnId"), time, mpHost);
} else {
// FIXME: There can be more than one timer with the same name - we will
// use only the FIRST one for now, but we really ought to enhance the
// API to handle more than one potential parent with the same name:
auto pParentTimer = mpHost->getTimerUnit()->findFirstTimer(parent);
if (!pParentTimer) {
return {-1, qsl("parent '%1' not found").arg(parent)};
}
pT = new TTimer(pParentTimer, mpHost);
}
pT->setTime(time);
pT->setIsFolder((timeout == 0 && function.isEmpty()));
pT->setTemporary(false);
// The name should be set after isTempTimer, as that is faster.
// Also for permanent timers it is easier to debug if it is set before
// registration:
pT->setName(name);
// This will lead to the generation of the ID number:
mpHost->getTimerUnit()->registerTimer(pT);
if (!pT->setScript(function)) {
const QString errMsg = pT->getError();
// Apparently this will call the TTimer::unregisterTimer(...) method:
delete pT;
return {-1, qsl("unable to compile \"%1\", reason: %2").arg(function, errMsg)};
}
pT->setIsActive(false);
updateEditor();
return {pT->getID(), QString()};
}
// No documentation available in wiki - internal function
QPair<int, QString> TLuaInterpreter::startTempTimer(double timeout, const QString& function, const bool repeating)
{
const QTime time = QTime(0, 0, 0, 0).addMSecs(qRound(timeout * 1000));
auto* pT = new TTimer(qsl("newTempTimerWithoutAnId"), time, mpHost, repeating);
pT->setTime(time);
pT->setIsFolder(false);
pT->setTemporary(true);
// This call leads to the allocation of an ID number - and its use as the
// name for temporary timers:
mpHost->getTimerUnit()->registerTimer(pT);
if (!pT->setScript(function)) {
const QString errMsg = pT->getError();
// Apparently this will call the TTimer::unregisterTimer(...) method:
delete pT;
return qMakePair(-1, qsl("unable to compile \"%1\", reason: %2").arg(function, errMsg));
}
const int id = pT->getID();
pT->setIsActive(true);
pT->enableTimer(id);
return qMakePair(id, QString());
}
// No documentation available in wiki - internal function
std::pair<int, QString> TLuaInterpreter::startPermAlias(const QString& name, const QString& parent, const QString& regex, const QString& function)
{
TAlias* pT;
if (parent.isEmpty()) {
pT = new TAlias("a", mpHost);
} else {
TAlias* pP = mpHost->getAliasUnit()->findFirstAlias(parent);
if (!pP) {
return {-1, qsl("parent '%1' not found").arg(parent)};
}
pT = new TAlias(pP, mpHost);
}
pT->setRegexCode(regex);
pT->setIsFolder((regex.isEmpty() && function.isEmpty()));
pT->setIsActive(true);
pT->setTemporary(false);
pT->registerAlias();
pT->setScript(function);
pT->setName(name);
updateEditor();
return {pT->getID(), QString()};
}
// No documentation available in wiki - internal function
int TLuaInterpreter::startTempAlias(const QString& regex, const QString& function)
{
TAlias* pT;
pT = new TAlias("a", mpHost);
pT->setRegexCode(regex);
pT->setIsFolder(false);
pT->setIsActive(true);
pT->setTemporary(true);
pT->registerAlias();
if (!function.isEmpty()) {
pT->setScript(function);
}
const int id = pT->getID();
pT->setName(QString::number(id));
return id;
}
// No documentation available in wiki - internal function
std::pair<int, QString> TLuaInterpreter::startPermKey(QString& name, QString& parent, int& keycode, int& modifier, QString& function)
{
TKey* pT;
if (parent.isEmpty()) {
pT = new TKey("a", mpHost); // The use of "a" seems a bit arbitrary...!
} else {
TKey* pP = mpHost->getKeyUnit()->findFirstKey(parent);
if (!pP) {
return {-1, qsl("parent '%1' not found").arg(parent)};
}
pT = new TKey(pP, mpHost);
}
pT->setKeyCode(keycode);
pT->setKeyModifiers(modifier);
pT->setIsFolder(keycode == -1);
pT->setIsActive(keycode != -1); // Folders (keycode == -1) start as inactive
pT->setTemporary(false);
pT->registerKey();
// CHECK: The lua code in function could fail to compile - but there is no feedback here to the caller.
pT->setScript(function);
pT->setName(name);
updateEditor();
return {pT->getID(), QString()};
}
// No documentation available in wiki - internal function
int TLuaInterpreter::startTempKey(int& modifier, int& keycode, const QString& function)
{
TKey* pT;
pT = new TKey("a", mpHost);
pT->setKeyCode(keycode);
pT->setKeyModifiers(modifier);
pT->setIsFolder(false);
pT->setIsActive(true);
pT->setTemporary(true);
pT->registerKey();
if (!function.isEmpty()) {
pT->setScript(function);
}
const int id = pT->getID();
pT->setName(QString::number(id));
return id;
}
// No documentation available in wiki - internal function
int TLuaInterpreter::startTempExactMatchTrigger(const QString& regex, const QString& function, int expiryCount)
{
TTrigger* pT = nullptr;
const QStringList sList{regex};
QList<int> const propertyList{REGEX_EXACT_MATCH};
pT = new TTrigger("a", sList, propertyList, false, mpHost);
pT->setIsFolder(false);
pT->setIsActive(true);
pT->setTemporary(true);
pT->registerTrigger();
pT->setScript(function);
const int id = pT->getID();
pT->setName(QString::number(id));
pT->setExpiryCount(expiryCount);
return id;
}
// No documentation available in wiki - internal function
int TLuaInterpreter::startTempBeginOfLineTrigger(const QString& regex, const QString& function, int expiryCount)
{
TTrigger* pT = nullptr;
const QStringList sList{regex};
QList<int> const propertyList{REGEX_BEGIN_OF_LINE_SUBSTRING};
pT = new TTrigger("a", sList, propertyList, false, mpHost);
pT->setIsFolder(false);
pT->setIsActive(true);
pT->setTemporary(true);
pT->registerTrigger();
pT->setScript(function);
const int id = pT->getID();
pT->setName(QString::number(id));
pT->setExpiryCount(expiryCount);
return id;
}
// No documentation available in wiki - internal function
int TLuaInterpreter::startTempTrigger(const QString& regex, const QString& function, int expiryCount)
{
TTrigger* pT = nullptr;
const QStringList sList{regex};
QList<int> const propertyList{REGEX_SUBSTRING};
pT = new TTrigger("a", sList, propertyList, false, mpHost);
pT->setIsFolder(false);
pT->setIsActive(true);
pT->setTemporary(true);
pT->registerTrigger();
pT->setScript(function);
const int id = pT->getID();
pT->setName(QString::number(id));
pT->setExpiryCount(expiryCount);
return id;
}
// No documentation available in wiki - internal function
int TLuaInterpreter::startTempPromptTrigger(const QString& function, int expiryCount)
{
TTrigger* pT;
const QStringList sList = {QString()};
QList<int> const propertyList = {REGEX_PROMPT};
pT = new TTrigger("a", sList, propertyList, false, mpHost);
pT->setIsFolder(false);
pT->setIsActive(true);
pT->setTemporary(true);
pT->registerTrigger();
pT->setScript(function);
const int id = pT->getID();
pT->setName(QString::number(id));
pT->setExpiryCount(expiryCount);
return id;
}
// No documentation available in wiki - internal function
int TLuaInterpreter::startTempLineTrigger(int from, int howmany, const QString& function, int expiryCount)
{
TTrigger* pT;
// QStringList sList;
// QList<int> propertyList;
// propertyList << REGEX_SUBSTRING;// substring trigger is default
// pT = new TTrigger("a", sList, propertyList, false, mpHost );
pT = new TTrigger(nullptr, mpHost);
pT->setIsFolder(false);
pT->setIsActive(true);
pT->setTemporary(true);
pT->setIsLineTrigger(true);
pT->setStartOfLineDelta(from);
pT->setLineDelta(howmany);
pT->registerTrigger();
pT->setScript(function);
const int id = pT->getID();
pT->setName(QString::number(id));
pT->setExpiryCount(expiryCount);
return id;
}
// No documentation available in wiki - internal function
int TLuaInterpreter::startTempColorTrigger(int fg, int bg, const QString& function, int expiryCount)
{
TTrigger* pT;
// QStringList sList;
// QList<int> propertyList;
// propertyList << REGEX_SUBSTRING;// substring trigger is default
// pT = new TTrigger("a", sList, propertyList, false, mpHost );
pT = new TTrigger(nullptr, mpHost);
pT->setIsFolder(false);
pT->setIsActive(true);
pT->setTemporary(true);
pT->setupTmpColorTrigger(fg, bg);
pT->registerTrigger();
pT->setScript(function);
const int id = pT->getID();
pT->setName(QString::number(id));
pT->setExpiryCount(expiryCount);
return id;
}
// No documentation available in wiki - internal function
int TLuaInterpreter::startTempRegexTrigger(const QString& regex, const QString& function, int expiryCount)
{
TTrigger* pT = nullptr;
const QStringList sList{regex};
QList<int> const propertyList{REGEX_PERL};
pT = new TTrigger("a", sList, propertyList, false, mpHost);
pT->setIsFolder(false);
pT->setIsActive(true);
pT->setTemporary(true);
pT->registerTrigger();
pT->setScript(function);
const int id = pT->getID();
pT->setName(QString::number(id));
pT->setExpiryCount(expiryCount);
return id;
}
// No documentation available in wiki - internal function
std::pair<int, QString> TLuaInterpreter::startPermRegexTrigger(const QString& name, const QString& parent, QStringList& patterns, const QString& function)
{
TTrigger* pT;
QList<int> propertyList;
for (int i = 0; i < patterns.size(); i++) {
propertyList << REGEX_PERL;
}
if (parent.isEmpty()) {
pT = new TTrigger("a", patterns, propertyList, (patterns.size() > 1), mpHost);
} else {
TTrigger* pP = mpHost->getTriggerUnit()->findTrigger(parent);
if (!pP) {
return std::pair(-1, qsl("parent '%1' not found").arg(parent));
}
pT = new TTrigger(pP, mpHost);
pT->setRegexCodeList(patterns, propertyList);
}
pT->setIsFolder(patterns.empty());
pT->setIsActive(true);
pT->setTemporary(false);
pT->registerTrigger();
pT->setScript(function);
pT->setName(name);
updateEditor();
return std::pair(pT->getID(), QString());
}
// No documentation available in wiki - internal function
std::pair<int, QString> TLuaInterpreter::startPermBeginOfLineStringTrigger(const QString& name, const QString& parent, QStringList& patterns, const QString& function)
{
TTrigger* pT;
QList<int> propertyList;
for (int i = 0; i < patterns.size(); i++) {
propertyList << REGEX_BEGIN_OF_LINE_SUBSTRING;
}
if (parent.isEmpty()) {
pT = new TTrigger("a", patterns, propertyList, (patterns.size() > 1), mpHost);
} else {
TTrigger* pP = mpHost->getTriggerUnit()->findTrigger(parent);
if (!pP) {
return {-1, qsl("parent '%1' not found").arg(parent)};
}
pT = new TTrigger(pP, mpHost);
pT->setRegexCodeList(patterns, propertyList);
}
pT->setIsFolder(patterns.empty());
pT->setIsActive(true);
pT->setTemporary(false);
pT->registerTrigger();
pT->setScript(function);
pT->setName(name);
updateEditor();
return std::pair(pT->getID(), QString());
}
// No documentation available in wiki - internal function
std::pair<int, QString> TLuaInterpreter::startPermSubstringTrigger(const QString& name, const QString& parent, const QStringList& patterns, const QString& function)
{
TTrigger* pT;
QList<int> propertyList;
for (int i = 0; i < patterns.size(); i++) {
propertyList << REGEX_SUBSTRING;
}
if (parent.isEmpty()) {
pT = new TTrigger("a", patterns, propertyList, (patterns.size() > 1), mpHost);
} else {
TTrigger* pP = mpHost->getTriggerUnit()->findTrigger(parent);
if (!pP) {
return {-1, qsl("parent '%1' not found").arg(parent)};
}
pT = new TTrigger(pP, mpHost);
pT->setRegexCodeList(patterns, propertyList);
}
pT->setIsFolder(patterns.empty());
pT->setIsActive(true);
pT->setTemporary(false);
pT->registerTrigger();
pT->setScript(function);
pT->setName(name);
updateEditor();
return {pT->getID(), QString()};
}
// No documentation available in wiki - internal function
std::pair<int, QString> TLuaInterpreter::startPermExactMatchTrigger(const QString& name, const QString& parent, const QStringList& patterns, const QString& function)
{
TTrigger* pT;
QList<int> propertyList;
for (int i = 0; i < patterns.size(); i++) {
propertyList << REGEX_EXACT_MATCH;
}
if (parent.isEmpty()) {
pT = new TTrigger("a", patterns, propertyList, (patterns.size() > 1), mpHost);
} else {
TTrigger* pP = mpHost->getTriggerUnit()->findTrigger(parent);
if (!pP) {
return {-1, qsl("parent '%1' not found").arg(parent)};
}
pT = new TTrigger(pP, mpHost);
pT->setRegexCodeList(patterns, propertyList);
}
pT->setIsFolder(patterns.empty());
pT->setIsActive(true);
pT->setTemporary(false);
pT->registerTrigger();
pT->setScript(function);
pT->setName(name);
updateEditor();
return {pT->getID(), QString()};
}
// No documentation available in wiki - internal function
std::pair<int, QString> TLuaInterpreter::startPermPromptTrigger(const QString& name, const QString& parent, const QString& function)
{
TTrigger* pT;
QList<int> const propertyList = {REGEX_PROMPT};
const QStringList patterns = {QString()};
if (parent.isEmpty()) {
pT = new TTrigger("a", patterns, propertyList, false, mpHost);
} else {
TTrigger* pP = mpHost->getTriggerUnit()->findTrigger(parent);
if (!pP) {
return {-1, qsl("parent '%1' not found").arg(parent)};
}
pT = new TTrigger(pP, mpHost);
pT->setRegexCodeList(patterns, propertyList);
}
pT->setIsFolder(false);
pT->setIsActive(true);
pT->setTemporary(false);
pT->registerTrigger();
pT->setScript(function);
pT->setName(name);
updateEditor();
return {pT->getID(), QString()};
}
// Documentation: https://wiki.mudlet.org/w/Manual:Lua_Functions#alert
int TLuaInterpreter::alert(lua_State* L)
{
double luaAlertDuration = 0.0;
if (lua_gettop(L) > 0) {
luaAlertDuration = getVerifiedDouble(L, __func__, 1, "alert duration in seconds");
if (luaAlertDuration < 0.000) {
lua_pushstring(L, "alert: duration, in seconds, is optional but if given must be zero or greater.");
return lua_error(L);
}
}
// QApplication::alert expects milliseconds, not seconds
QApplication::alert(mudlet::self(), qRound(luaAlertDuration * 1000.0));
return 0;
}
static int host_key = 0;
// No documentation available in wiki - internal function
static void storeHostInLua(lua_State* L, Host* h)
{
lua_pushlightuserdata(L, &host_key); // 1 - push unique key
lua_pushlightuserdata(L, h); // 2 - push host ptr
lua_rawset(L, LUA_REGISTRYINDEX); // 0 - register[key] = host
}
// No documentation available in wiki - internal function
Host& getHostFromLua(lua_State* L)
{
lua_pushlightuserdata(L, &host_key); // 1 - push unique key
lua_rawget(L, LUA_REGISTRYINDEX); // 1 - pop key, push host ptr
auto* h = static_cast<Host*>(lua_touserdata(L, -1)); // 1 - get host ptr
lua_pop(L, 1); // 0 - pop host ptr
assert(h);
return *h;
}
// No documentation available in wiki - internal function
// Used to unref lua objects in the registry to avoid memory leaks
// i.e. Unrefing tables passed into TLabel's event parameters.
void TLuaInterpreter::freeLuaRegistryIndex(int index)
{
luaL_unref(pGlobalLua, LUA_REGISTRYINDEX, index);
}
// No documentation available in wiki - internal function
// Looks for argument types in an 'event' that have stored
// data in the lua registry, and frees this data.
void TLuaInterpreter::freeAllInLuaRegistry(TEvent event)
{
for (int i = 0; i < event.mArgumentList.size(); i++) {
if (event.mArgumentTypeList.at(i) == ARGUMENT_TYPE_TABLE || event.mArgumentTypeList.at(i) == ARGUMENT_TYPE_FUNCTION) {
freeLuaRegistryIndex(event.mArgumentList.at(i).toInt());
}
}
}
// Documentation: https://wiki.mudlet.org/w/Manual:Lua_Functions#addWordToDictionary
int TLuaInterpreter::addWordToDictionary(lua_State* L)
{
Host& host = getHostFromLua(L);
bool hasUserDictionary = false;
bool hasSharedDictionary = false;
host.getUserDictionaryOptions(hasUserDictionary, hasSharedDictionary);
if (!hasUserDictionary) {
return warnArgumentValue(L, __func__, "no user dictionary enabled in the preferences for this profile");
}
const QString text = getVerifiedString(L, __func__, 1, "word");
QPair<bool, QString> const result = host.mpConsole->addWordToSet(text);
if (!result.first) {
return warnArgumentValue(L, __func__, result.second.toUtf8().constData());
}
lua_pushboolean(L, true);
return 1;
}
// Documentation: https://wiki.mudlet.org/w/Manual:Lua_Functions#removeWordFromDictionary
int TLuaInterpreter::removeWordFromDictionary(lua_State* L)
{
Host& host = getHostFromLua(L);
bool hasUserDictionary = false;
bool hasSharedDictionary = false;
host.getUserDictionaryOptions(hasUserDictionary, hasSharedDictionary);
if (!hasUserDictionary) {
return warnArgumentValue(L, __func__, "no user dictionary enabled in the preferences for this profile");
}
const QString text = getVerifiedString(L, __func__, 1, "word");
QPair<bool, QString> const result = host.mpConsole->removeWordFromSet(text);
if (!result.first) {
return warnArgumentValue(L, __func__, result.second.toUtf8().constData());
}
lua_pushboolean(L, true);
return 1;
}
// Documentation: https://wiki.mudlet.org/w/Manual:Lua_Functions#spellCheckWord
int TLuaInterpreter::spellCheckWord(lua_State* L)
{
Host& host = getHostFromLua(L);
bool hasUserDictionary = false;
bool hasSharedDictionary = false;
host.getUserDictionaryOptions(hasUserDictionary, hasSharedDictionary);
if (!checkStringArg(L, __func__, 1, "word")) {
return lua_error(L);
}
bool useUserDictionary = false;
if (lua_gettop(L) > 1) {
useUserDictionary = getVerifiedBool(L, __func__, 2, "check profile dictionary {use 'false' or omit to check against system dictionary}", true);
if (useUserDictionary && !hasUserDictionary) {
return warnArgumentValue(L, __func__, "no user dictionary enabled in the preferences for this profile");
}
}
const QString text{lua_tostring(L, 1)};
Hunhandle* handle = nullptr;
QByteArray encodedText;
if (useUserDictionary) {
handle = host.mpConsole->getHunspellHandle_user();
encodedText = text.toUtf8();
} else {
handle = host.mpConsole->getHunspellHandle_system();
if (!handle) {
return warnArgumentValue(L, __func__, "no main dictionaries found: Mudlet has not been able to find any dictionary files to use so is unable to check your word");
}
encodedText = TEncodingHelper::encode(text, host.mpConsole->getHunspellCodecName_system());
}
// CHECKME: Is there any danger of contention here - do we need to get mudlet::mDictionaryReadWriteLock locked for reading if we are accessing the shared user dictionary?
lua_pushboolean(L, Hunspell_spell(handle, encodedText.constData()));
return 1;
}
// Documentation: https://wiki.mudlet.org/w/Manual:Lua_Functions#spellSuggestWord
int TLuaInterpreter::spellSuggestWord(lua_State* L)
{
Host& host = getHostFromLua(L);
bool hasUserDictionary = false;
bool hasSharedDictionary = false;
host.getUserDictionaryOptions(hasUserDictionary, hasSharedDictionary);
if (!checkStringArg(L, __func__, 1, "word")) {
return lua_error(L);
}
bool useUserDictionary = false;
if (lua_gettop(L) > 1) {
useUserDictionary = getVerifiedBool(L, __func__, 2, "check profile dictionary {use 'false' or omit to check against system dictionary}", true);
if (useUserDictionary && !hasUserDictionary) {
return warnArgumentValue(L, __func__, "no user dictionary enabled in the preferences for this profile");
}
}
const QString text{lua_tostring(L, 1)};
char** wordList;
size_t wordCount = 0;
Hunhandle* handle = nullptr;
QByteArray encodedText;
if (useUserDictionary) {
handle = host.mpConsole->getHunspellHandle_user();
encodedText = text.toUtf8();
} else {
handle = host.mpConsole->getHunspellHandle_system();
if (!handle) {
return warnArgumentValue(L, __func__, "no main dictionaries found: Mudlet has not been able to find any dictionary files to use so is unable to make suggestions for your word");
}
encodedText = TEncodingHelper::encode(text, host.mpConsole->getHunspellCodecName_system());
}
// CHECKME: Is there any danger of contention here - do we need to get mudlet::mDictionaryReadWriteLock locked for reading if we are accessing the shared user dictionary?
wordCount = Hunspell_suggest(handle, &wordList, encodedText.constData());
lua_newtable(L);
for (size_t i = 0; i < wordCount; ++i) {
lua_pushnumber(L, i + 1);
QString suggestion;
if (hasUserDictionary) {
suggestion = QString::fromUtf8(wordList[i]);
} else {
suggestion = TEncodingHelper::decode(QByteArray(wordList[i]), host.mpConsole->getHunspellCodecName_system());
}
lua_pushstring(L, suggestion.toUtf8().constData());
lua_settable(L, -3);
}
Hunspell_free_list(handle, &wordList, wordCount);
return 1;
}
// Documentation: https://wiki.mudlet.org/w/Manual:Lua_Functions#getDictionaryWordList
int TLuaInterpreter::getDictionaryWordList(lua_State* L)
{
Host& host = getHostFromLua(L);
bool hasUserDictionary = false;
bool hasSharedDictionary = false;
host.getUserDictionaryOptions(hasUserDictionary, hasSharedDictionary);
if (!hasUserDictionary) {
return warnArgumentValue(L, __func__, "no user dictionary enabled in the preferences for this profile");
}
// This may stall if this is accessing the shared user dictionary and that
// is being updated by another profile, but it should eventually return...
// We must keep a local reference/copy of the value returned because the
// returned item is a deep-copy in the case of a shared dictionary and two
// calls to TConsole::getWordSet() can return two different instances which
// is fatally dangerous if used in a range based initialiser:
QSet<QString> wordSet{host.mpConsole->getWordSet()};
QStringList wordList{wordSet.begin(), wordSet.end()};
const int wordCount = wordList.size();
if (wordCount > 1) {
QCollator sorter;
sorter.setCaseSensitivity(Qt::CaseInsensitive);
std::sort(wordList.begin(), wordList.end(), sorter);
}
lua_newtable(L);
for (int i = 0; i < wordCount; ++i) {
lua_pushinteger(L, i + 1);
lua_pushstring(L, wordList.at(i).toUtf8().constData());
lua_settable(L, -3);
}
return 1;
}
// Documentation: https://wiki.mudlet.org/w/Manual:Miscellaneous_Functions#getCharacterName
int TLuaInterpreter::getCharacterName(lua_State* L)
{
Host& host = getHostFromLua(L);
const QString name{host.getLogin()};
if (name.isEmpty()) {
lua_pushnil(L);
lua_pushstring(L, "no character name set");
return 2;
}
lua_pushstring(L, name.toUtf8().constData());
return 1;
}
// Documentation: https://wiki.mudlet.org/w/Manual:Miscellaneous_Functions#getProfileInformation
int TLuaInterpreter::getProfileInformation(lua_State* L)
{
QString info;
Host& host = getHostFromLua(L);
const int params = lua_gettop(L);
switch (params) {
case 0: {
info = host.readProfileData(qsl("description"));
break;
}
default: {
const QString requestedName = getVerifiedString(L, __func__, 1, "profile name");
if (requestedName.isEmpty()) {
lua_pushnil(L);
lua_pushstring(L, "getProfileInformation: profile name cannot be empty");
return 2;
}
const QString profileName = mudlet::self()->getCanonicalProfileName(requestedName);
if (profileName.isEmpty()) {
lua_pushnil(L);
lua_pushfstring(L, "getProfileInformation: profile '%s' does not exist", requestedName.toUtf8().constData());
return 2;
}
info = mudlet::self()->readProfileData(profileName, qsl("description"));
break;
}
}
lua_pushstring(L, info.toUtf8().constData());
return 1;
}
// No documentation available in wiki - internal function
// The folder a profile name resolves to, or an empty string if there is no such
// profile. For writers, and so stricter than mudlet::getCanonicalProfileName(),
// which also resolves a game Mudlet ships with that has never been opened:
// writeProfileData() creates whatever folder it is handed, so writing under such
// a name would turn that game into a profile of its own. Readers want the looser
// call.
static QString canonicalProfileFolder(const QString& profileName)
{
const QString folder = mudlet::self()->getCanonicalProfileName(profileName);
if (folder.isEmpty() || !QDir(mudlet::getMudletPath(enums::profileHomePath, folder)).exists()) {
return QString();
}
return folder;
}
// Documentation: https://wiki.mudlet.org/w/Manual:Miscellaneous_Functions#setProfileInformation
int TLuaInterpreter::setProfileInformation(lua_State* L)
{
const int params = lua_gettop(L);
if (params == 1) {
if (!checkStringArg(L, __func__, 1, "text")) {
return lua_error(L);
}
} else if (!checkStringArg(L, __func__, 1, "profile name") || !checkStringArg(L, __func__, 2, "text")) {
return lua_error(L);
}
QString profileName = getHostFromLua(L).getName();
QString text;
if (params == 1) {
text = lua_tostring(L, 1);
} else {
const QString requestedName = lua_tostring(L, 1);
profileName = canonicalProfileFolder(requestedName);
if (profileName.isEmpty()) {
return warnArgumentValue(L, __func__, qsl("profile '%1' does not exist").arg(requestedName));
}
text = lua_tostring(L, 2);
}
const QPair<bool, QString> result = mudlet::self()->writeProfileData(profileName, qsl("description"), text);
if (!result.first) {
return warnArgumentValue(L, __func__, result.second);
}
lua_pushboolean(L, true);
return 1;
}
// Documentation: https://wiki.mudlet.org/w/Manual:Miscellaneous_Functions#clearProfileInformation
int TLuaInterpreter::clearProfileInformation(lua_State* L)
{
const int params = lua_gettop(L);
if (params > 0 && !checkStringArg(L, __func__, 1, "profile name")) {
return lua_error(L);
}
QString profileName = getHostFromLua(L).getName();
if (params > 0) {
const QString requestedName = lua_tostring(L, 1);
profileName = canonicalProfileFolder(requestedName);
if (profileName.isEmpty()) {
return warnArgumentValue(L, __func__, qsl("profile '%1' does not exist").arg(requestedName));
}
}
QString desc = "";
// if this is a default game, return to the orginal text
auto itDetails = TGameDetails::findGame(profileName);
if (itDetails != TGameDetails::scmDefaultGames.constEnd()) {
if (!(*itDetails).description.isEmpty()) {
desc = (*itDetails).description;
}
}
const QPair<bool, QString> result = mudlet::self()->writeProfileData(profileName, qsl("description"), desc);
if (!result.first) {
return warnArgumentValue(L, __func__, result.second);
}
lua_pushboolean(L, true);
return 1;
}
// Internal function - helper for updateColorTable().
void TLuaInterpreter::insertColorTableEntry(lua_State* L, const QColor& color, const QString& name)
{
// Equivalent (when called from updateColorTable()) to Lua (where the
// '<8-bit unsigned int, i.e. 0 to 255>'s are provided from the QColor):
// color_table["name"] = { <color.red()>, <color.green()>, <color.blue()> }
// Creates a new empty table on the stack with space preallocated for 3
// array elements and 0 non-array elements:
lua_createtable(L, 3, 0);
lua_pushnumber(L, color.red());
lua_rawseti(L, -2, 1);
lua_pushnumber(L, color.green());
lua_rawseti(L, -2, 2);
lua_pushnumber(L, color.blue());
lua_rawseti(L, -2, 3);
lua_getfield(L, LUA_GLOBALSINDEX, "color_table");
lua_insert(L, -2);
lua_pushstring(L, name.toUtf8().constData());
lua_insert(L, -2);
lua_settable(L, -3);
lua_pop(L, 1);
}
// Internal function - copies current profile's 16 ANSI colors into the Lua "color_table"
void TLuaInterpreter::updateAnsi16ColorsInTable()
{
lua_State* L = pGlobalLua;
// Does the color_table already exist:
// Equivalent to Lua:
// color_table = color_table or {}
lua_getfield(L, LUA_GLOBALSINDEX, "color_table");
if (!(lua_toboolean(L, -1))) {
// no it doesn't
lua_pop(L, 1);
// So make it
lua_newtable(L);
}
// Okay so now we point ourselves at the wanted table:
lua_setfield(L, LUA_GLOBALSINDEX, "color_table");
// Now we can add/update the items we need to, though it is a bit repetitive:
QColor color = mpHost->mBlack;
insertColorTableEntry(L, color, qsl("ansi_000"));
insertColorTableEntry(L, color, qsl("ansi_black"));
insertColorTableEntry(L, color, qsl("ansiBlack"));
color = mpHost->mRed;
insertColorTableEntry(L, color, qsl("ansi_001"));
insertColorTableEntry(L, color, qsl("ansi_red"));
insertColorTableEntry(L, color, qsl("ansiRed"));
color = mpHost->mGreen;
insertColorTableEntry(L, color, qsl("ansi_002"));
insertColorTableEntry(L, color, qsl("ansi_green"));
insertColorTableEntry(L, color, qsl("ansiGreen"));
color = mpHost->mYellow;
insertColorTableEntry(L, color, qsl("ansi_003"));
insertColorTableEntry(L, color, qsl("ansi_yellow"));
insertColorTableEntry(L, color, qsl("ansiYellow"));
color = mpHost->mBlue;
insertColorTableEntry(L, color, qsl("ansi_004"));
insertColorTableEntry(L, color, qsl("ansi_blue"));
insertColorTableEntry(L, color, qsl("ansiBlue"));
color = mpHost->mMagenta;
insertColorTableEntry(L, color, qsl("ansi_005"));
insertColorTableEntry(L, color, qsl("ansi_magenta"));
insertColorTableEntry(L, color, qsl("ansiMagenta"));
color = mpHost->mCyan;
insertColorTableEntry(L, color, qsl("ansi_006"));
insertColorTableEntry(L, color, qsl("ansi_cyan"));
insertColorTableEntry(L, color, qsl("ansiCyan"));
color = mpHost->mWhite;
insertColorTableEntry(L, color, qsl("ansi_007"));
insertColorTableEntry(L, color, qsl("ansi_white"));
insertColorTableEntry(L, color, qsl("ansiWhite"));
color = mpHost->mLightBlack;
insertColorTableEntry(L, color, qsl("ansi_008"));
insertColorTableEntry(L, color, qsl("ansi_light_black"));
insertColorTableEntry(L, color, qsl("ansiLightBlack"));
color = mpHost->mLightRed;
insertColorTableEntry(L, color, qsl("ansi_009"));
insertColorTableEntry(L, color, qsl("ansi_light_red"));
insertColorTableEntry(L, color, qsl("ansiLightRed"));
color = mpHost->mLightGreen;
insertColorTableEntry(L, color, qsl("ansi_010"));
insertColorTableEntry(L, color, qsl("ansi_light_green"));
insertColorTableEntry(L, color, qsl("ansiLightGreen"));
color = mpHost->mLightYellow;
insertColorTableEntry(L, color, qsl("ansi_011"));
insertColorTableEntry(L, color, qsl("ansi_light_yellow"));
insertColorTableEntry(L, color, qsl("ansiLightYellow"));
color = mpHost->mLightBlue;
insertColorTableEntry(L, color, qsl("ansi_012"));
insertColorTableEntry(L, color, qsl("ansi_light_blue"));
insertColorTableEntry(L, color, qsl("ansiLightBlue"));
color = mpHost->mLightMagenta;
insertColorTableEntry(L, color, qsl("ansi_013"));
insertColorTableEntry(L, color, qsl("ansi_light_magenta"));
insertColorTableEntry(L, color, qsl("ansiLightMagenta"));
color = mpHost->mLightCyan;
insertColorTableEntry(L, color, qsl("ansi_014"));
insertColorTableEntry(L, color, qsl("ansi_light_cyan"));
insertColorTableEntry(L, color, qsl("ansiLightCyan"));
color = mpHost->mLightWhite;
insertColorTableEntry(L, color, qsl("ansi_015"));
insertColorTableEntry(L, color, qsl("ansi_light_white"));
insertColorTableEntry(L, color, qsl("ansiLightWhite"));
}
// Internal function - copies current profile's extended ANSI colors into the
// Lua "color_table" - it might be feasible to do this entirely within an
// external lua file ("GUIUtils.lua2) as we do not provide a means to vary
// the ANSI colours 17 to 255 that this handles...
void TLuaInterpreter::updateExtendedAnsiColorsInTable()
{
lua_State* L = pGlobalLua;
// Does the color_table already exist:
// Equivalent to Lua:
// color_table = color_table or {}
lua_getfield(L, LUA_GLOBALSINDEX, "color_table");
if (!(lua_toboolean(L, -1))) {
// no it doesn't
lua_pop(L, 1);
// So make it
lua_newtable(L);
}
// Okay so now we point ourselves at the wanted table:
lua_setfield(L, LUA_GLOBALSINDEX, "color_table");
// And insert the 6x6x6 RGB colours
for (int i = 0; i < 216; ++i) {
const int r = i / 36;
const int g = (i - (r * 36)) / 6;
const int b = (i - (r * 36)) - (g * 6);
lua_createtable(L, 3, 0);
lua_pushnumber(L, r == 0 ? 0 : (r - 1) * 40 + 95);
lua_rawseti(L, -2, 1);
lua_pushnumber(L, g == 0 ? 0 : (g - 1) * 40 + 95);
lua_rawseti(L, -2, 2);
lua_pushnumber(L, b == 0 ? 0 : (b - 1) * 40 + 95);
lua_rawseti(L, -2, 3);
lua_getfield(L, LUA_GLOBALSINDEX, "color_table");
lua_insert(L, -2);
const QString name = qsl("ansi_%1").arg(i + 16, 3, 10, QLatin1Char('0'));
lua_pushstring(L, name.toUtf8().constData());
lua_insert(L, -2);
lua_settable(L, -3);
lua_pop(L, 1);
}
// And insert the 24 Greyscale colours
for (int i = 232; i < 256; ++i) {
lua_createtable(L, 3, 0);
const int value = (i - 232) * 10 + 8;
lua_pushnumber(L, value);
lua_rawseti(L, -2, 1);
lua_pushnumber(L, value);
lua_rawseti(L, -2, 2);
lua_pushnumber(L, value);
lua_rawseti(L, -2, 3);
lua_getfield(L, LUA_GLOBALSINDEX, "color_table");
lua_insert(L, -2);
const QString name = qsl("ansi_%1").arg(i, 3, 10, QLatin1Char('0'));
lua_pushstring(L, name.toUtf8().constData());
lua_insert(L, -2);
lua_settable(L, -3);
lua_pop(L, 1);
}
}
// Internal function - Creates a table for useful information from the http
// response. It creates an empty table, calls upon other functions to
// add things to it, and then returns a key to where it is in the lua registry.
int TLuaInterpreter::createHttpResponseTable(QNetworkReply* reply)
{
lua_State* L = pGlobalLua;
// Push empty table onto stack
lua_newtable(L);
// Add "headers" table to table
createHttpHeadersTable(L, reply);
// Add "cookies" table to table
createCookiesTable(L, reply);
// Pop table from stack, store it in registry, return key to it
return luaL_ref(L, LUA_REGISTRYINDEX);
}
// Internal function - Adds an empty table to a "headers" key to the table for
// tracking useful information from the http response. If there are any headers
// in the http response, it adds them to this new empty table.
void TLuaInterpreter::createHttpHeadersTable(lua_State* L, QNetworkReply* reply)
{
// Assert table from createHttpResponseTable is where we expect it to be
if (!lua_istable(L, -1)) {
qDebug() << "Unable to find table at top of lua stack, aborting!";
return;
}
// Push "headers" key and empty table value onto stack
lua_pushstring(L, "headers");
lua_newtable(L);
// Parse headers, add them as key-value pairs to the empty table
const QList<QByteArray> headerList = reply->rawHeaderList();
for (const QByteArray& header : headerList) {
// Push header key onto stack
lua_pushstring(L, header.constData());
// Push header value onto stack
lua_pushstring(L, reply->rawHeader(header).constData());
// Put key-value pair into table (now 3 deep in stack), pop stack twice
lua_settable(L, -3);
}
// Put "headers" table into table (now 3 deep in stack), pop stack twice
lua_settable(L, -3);
}
// Internal function - Adds an empty table to a "cookies" key to the table for
// tracking useful information from the http response. If there are any cookies
// in the http response, it adds them to this new empty table.
void TLuaInterpreter::createCookiesTable(lua_State* L, QNetworkReply* reply)
{
// Assert table from createHttpResponseTable is where we expect it to be
if (!lua_istable(L, -1)) {
qDebug() << "Unable to find table at top of lua stack, aborting!";
return;
}
// Push "cookies" key and empty table value onto stack
lua_pushstring(L, "cookies");
lua_newtable(L);
// Parse cookies, add them as key-value pairs to the empty table
const Host& host = getHostFromLua(L);
QNetworkCookieJar* cookieJar = host.mLuaInterpreter.mpFileDownloader->cookieJar();
const QList<QNetworkCookie> cookies = cookieJar->cookiesForUrl(reply->url());
for (const QNetworkCookie& cookie : cookies) {
// Push cookie name onto stack
lua_pushstring(L, cookie.name().constData());
// Push cookie value onto stack
lua_pushstring(L, cookie.value().constData());
// Put key-value pair into table (now 3 deep in stack), pop stack twice
lua_settable(L, -3);
}
// Put "cookies" table into table (now 3 deep in stack), pop stack twice
lua_settable(L, -3);
}
// Documentation: https://wiki.mudlet.org/w/Manual:Lua_Functions#getMapBackgroundColor
int TLuaInterpreter::getMapBackgroundColor(lua_State* L)
{
auto& host = getHostFromLua(L);
auto color = host.mBgColor_2;
lua_pushnumber(L, color.red());
lua_pushnumber(L, color.green());
lua_pushnumber(L, color.blue());
lua_pushnumber(L, color.alpha());
return 4;
}
// Documentation: https://wiki.mudlet.org/w/Manual:Lua_Functions#setMapBackgroundColor
int TLuaInterpreter::setMapBackgroundColor(lua_State* L)
{
int a = 255;
const int r = getVerifiedInt(L, __func__, 1, "red component");
if (r < 0 || r > 255) {
return warnArgumentValue(L, __func__, csmInvalidRedValue.arg(r));
}
const int g = getVerifiedInt(L, __func__, 2, "green component");
if (g < 0 || g > 255) {
return warnArgumentValue(L, __func__, csmInvalidGreenValue.arg(g));
}
const int b = getVerifiedInt(L, __func__, 3, "blue component");
if (b < 0 || b > 255) {
return warnArgumentValue(L, __func__, csmInvalidBlueValue.arg(b));
}
if (lua_gettop(L) > 3) {
a = getVerifiedInt(L, __func__, 4, "alpha", true);
if (a < 0 || a > 255) {
return warnArgumentValue(L, __func__, csmInvalidAlphaValue.arg(a));
}
}
auto& host = getHostFromLua(L);
host.mBgColor_2 = QColor(r, g, b, a);
updateMap(L);
lua_pushboolean(L, true);
return 1;
}
// Documentation: https://wiki.mudlet.org/w/Manual:Lua_Functions#getMapRoomExitsColor
int TLuaInterpreter::getMapRoomExitsColor(lua_State* L)
{
auto& host = getHostFromLua(L);
auto color = host.mFgColor_2;
lua_pushnumber(L, color.red());
lua_pushnumber(L, color.green());
lua_pushnumber(L, color.blue());
return 3;
}
// Documentation: https://wiki.mudlet.org/w/Manual:Lua_Functions#setMapRoomExitsColor
int TLuaInterpreter::setMapRoomExitsColor(lua_State* L)
{
const int r = getVerifiedInt(L, __func__, 1, "red component");
if (r < 0 || r > 255) {
return warnArgumentValue(L, __func__, csmInvalidRedValue.arg(r));
}
const int g = getVerifiedInt(L, __func__, 2, "green component");
if (g < 0 || g > 255) {
return warnArgumentValue(L, __func__, csmInvalidGreenValue.arg(g));
}
const int b = getVerifiedInt(L, __func__, 3, "blue component");
if (b < 0 || b > 255) {
return warnArgumentValue(L, __func__, csmInvalidBlueValue.arg(b));
}
auto& host = getHostFromLua(L);
host.mFgColor_2 = QColor(r, g, b);
updateMap(L);
lua_pushboolean(L, true);
return 1;
}
// Documentation: https://wiki.mudlet.org/w/Manual:Lua_Functions#showNotification
int TLuaInterpreter::showNotification(lua_State* L)
{
const int n = lua_gettop(L);
if (!checkStringArg(L, __func__, 1, "title") || (n >= 2 && !checkStringArg(L, __func__, 2, "message")) || (n >= 3 && !checkNumberArg(L, __func__, 3, "expiration time in seconds"))) {
return lua_error(L);
}
std::optional<int> notificationExpirationTime;
if (n >= 3) {
notificationExpirationTime = qMax(qRound(lua_tonumber(L, 3) * 1000), 1000);
}
const QString title{lua_tostring(L, 1)};
const QString text = (n >= 2) ? QString{lua_tostring(L, 2)} : title;
mudlet::self()->mTrayIcon.show();
if (notificationExpirationTime.has_value()) {
mudlet::self()->mTrayIcon.showMessage(title, text, mudlet::self()->mTrayIcon.icon(), notificationExpirationTime.value());
} else {
mudlet::self()->mTrayIcon.showMessage(title, text, mudlet::self()->mTrayIcon.icon());
}
mudlet::self()->mTrayIcon.hide();
lua_pushboolean(L, true);
return 1;
}
// Documentation: https://wiki.mudlet.org/w/Manual:Lua_Functions#addFileWatch
int TLuaInterpreter::addFileWatch(lua_State* L)
{
auto path = getVerifiedString(L, __func__, 1, "path");
auto& host = getHostFromLua(L);
const QFileInfo fileInfo(path);
if (!fileInfo.exists()) {
return warnArgumentValue(L, __func__, qsl("path '%1' does not exist").arg(path));
}
lua_pushboolean(L, host.getLuaInterpreter()->mpFileSystemWatcher->addPath(path));
return 1;
}
// Documentation: https://wiki.mudlet.org/w/Manual:Lua_Functions#removeFileWatch
int TLuaInterpreter::removeFileWatch(lua_State* L)
{
auto path = getVerifiedString(L, __func__, 1, "path");
auto& host = getHostFromLua(L);
lua_pushboolean(L, host.getLuaInterpreter()->mpFileSystemWatcher->removePath(path));
return 1;
}
// Documentation: https://wiki.mudlet.org/w/Manual:Lua_Functions#setConfig
// Please use same options with same names in setConfig and getConfig and keep them in sync
// The table argument case for setting multiple properties at once is handled
// by setConfig in Other.lua.
int TLuaInterpreter::setConfig(lua_State* L)
{
auto& host = getHostFromLua(L);
const bool currentHost = (mudlet::self()->mpCurrentActiveHost == &host);
if (!checkStringArg(L, __func__, 1, "key")) {
return lua_error(L);
}
// a view rather than a QString because the getVerified*() calls below raise -
// see checkStringArg(). Every comparison is against an ASCII literal, so
// reading the key as Latin-1 picks the branch a UTF-8 QString would; the two
// places that show the key decode it as UTF-8 there and then
const QLatin1StringView key{lua_tostring(L, 1)};
if (key.isEmpty()) {
return warnArgumentValue(L, __func__, "you must provide key");
}
auto success = [&]() {
if (mudlet::smDebugMode) {
TDebug(Qt::white, Qt::blue) << qsl("setConfig: a script has changed %1\n").arg(QString::fromUtf8(lua_tostring(L, 1))) >> &host;
}
lua_pushboolean(L, true);
return 1;
};
if (host.mpMap && host.mpMap->mpMapper) {
if (key == qsl("mapRoomSize")) {
host.mpMap->mpMapper->slot_roomSize(getVerifiedInt(L, __func__, 2, "value"));
return success();
}
if (key == qsl("mapExitSize")) {
host.mpMap->mpMapper->slot_exitSize(getVerifiedInt(L, __func__, 2, "value"));
return success();
}
if (key == qsl("mapRoundRooms")) {
host.mpMap->mpMapper->slot_toggleRoundRooms(getVerifiedBool(L, __func__, 2, "value"));
return success();
}
if (key == qsl("showRoomIdsOnMap")) {
host.mpMap->mpMapper->slot_setShowRoomIds(getVerifiedBool(L, __func__, 2, "value"));
return success();
}
if (key == qsl("showMapInfo")) {
host.mMapInfoContributors.insert(getVerifiedString(L, __func__, 2, "value"));
host.mpMap->mpMapper->slot_updateInfoContributors();
return success();
}
if (key == qsl("hideMapInfo")) {
host.mMapInfoContributors.remove(getVerifiedString(L, __func__, 2, "value"));
host.mpMap->mpMapper->slot_updateInfoContributors();
return success();
}
#if defined(INCLUDE_3DMAPPER)
if (key == qsl("show3dMapView")) {
host.mpMap->mpMapper->slot_toggle3DView(getVerifiedBool(L, __func__, 2, "value"));
return success();
}
#endif
if (key == qsl("mapperPanelVisible")) {
host.mpMap->mpMapper->slot_setMapperPanelVisible(getVerifiedBool(L, __func__, 2, "value"));
return success();
}
if (key == qsl("mapShowRoomBorders")) {
host.mMapperShowRoomBorders = getVerifiedBool(L, __func__, 2, "value");
return success();
}
if (key == qsl("mapShowGrid")) {
const bool showGrid = getVerifiedBool(L, __func__, 2, "value");
host.mMapperShowGrid = showGrid;
host.mpMap->mpMapper->slot_setShowGrid(showGrid);
return success();
}
if (key == qsl("showUpperLowerLevels")) {
mudlet::self()->mDrawUpperLowerLevels = getVerifiedBool(L, __func__, 2, "value");
if (host.mpMap && host.mpMap->mpMapper && host.mpMap->mpMapper->mp2dMap) {
host.mpMap->mpMapper->mp2dMap->update();
}
return success();
}
if (key == qsl("mapInfoColor")) {
if (!lua_istable(L, 2)) {
lua_pushfstring(L, "%s: bad argument #%d type (table expected for mapInfoColor, got %s!)", __func__, 2, luaL_typename(L, 2));
return warnArgumentValue(L, __func__, qsl("mapInfoColor requires a table {r, g, b} or {r, g, b, a}"));
}
// Get red component (index 1)
lua_rawgeti(L, 2, 1);
if (!lua_isnumber(L, -1)) {
lua_pop(L, 1);
return warnArgumentValue(L, __func__, qsl("mapInfoColor table must have red component at index 1"));
}
const int r = static_cast<int>(lua_tonumber(L, -1));
lua_pop(L, 1);
if (r < 0 || r > 255) {
return warnArgumentValue(L, __func__, csmInvalidRedValue.arg(r));
}
// Get green component (index 2)
lua_rawgeti(L, 2, 2);
if (!lua_isnumber(L, -1)) {
lua_pop(L, 1);
return warnArgumentValue(L, __func__, qsl("mapInfoColor table must have green component at index 2"));
}
const int g = static_cast<int>(lua_tonumber(L, -1));
lua_pop(L, 1);
if (g < 0 || g > 255) {
return warnArgumentValue(L, __func__, csmInvalidGreenValue.arg(g));
}
// Get blue component (index 3)
lua_rawgeti(L, 2, 3);
if (!lua_isnumber(L, -1)) {
lua_pop(L, 1);
return warnArgumentValue(L, __func__, qsl("mapInfoColor table must have blue component at index 3"));
}
const int b = static_cast<int>(lua_tonumber(L, -1));
lua_pop(L, 1);
if (b < 0 || b > 255) {
return warnArgumentValue(L, __func__, csmInvalidBlueValue.arg(b));
}
// Get alpha component (index 4, optional, defaults to 255)
int a = 255;
lua_rawgeti(L, 2, 4);
if (lua_isnumber(L, -1)) {
a = static_cast<int>(lua_tonumber(L, -1));
if (a < 0 || a > 255) {
lua_pop(L, 1);
return warnArgumentValue(L, __func__, csmInvalidAlphaValue.arg(a));
}
}
lua_pop(L, 1);
host.mMapInfoBg = QColor(r, g, b, a);
updateMap(L);
return success();
}
}
if (key == qsl("enableGMCP")) {
host.mEnableGMCP = getVerifiedBool(L, __func__, 2, "value");
return success();
}
if (key == qsl("enableMSSP")) {
host.mEnableMSSP = getVerifiedBool(L, __func__, 2, "value");
return success();
}
if (key == qsl("enableMSDP")) {
host.mEnableMSDP = getVerifiedBool(L, __func__, 2, "value");
return success();
}
if (key == qsl("enableMSP")) {
host.mEnableMSP = getVerifiedBool(L, __func__, 2, "value");
return success();
}
if (key == qsl("enableMTTS")) {
host.mEnableMTTS = getVerifiedBool(L, __func__, 2, "value");
return success();
}
if (key == qsl("enableMNES")) {
host.mEnableMNES = getVerifiedBool(L, __func__, 2, "value");
return success();
}
if (key == qsl("specialForceMxpNegotiationOff")) {
// specialForceMxpNegotiationOff should not be used anymore, but we support it for compatibility
// it will be the inverse of enableMXP
host.mEnableMXP = !getVerifiedBool(L, __func__, 2, "value");
return success();
}
if (key == qsl("enableMXP")) {
host.mEnableMXP = getVerifiedBool(L, __func__, 2, "value");
return success();
}
if (key == qsl("enableNAWS")) {
host.mEnableNAWS = getVerifiedBool(L, __func__, 2, "value");
return success();
}
if (key == qsl("undoServerWrap")) {
host.mUndoServerWrap = getVerifiedBool(L, __func__, 2, "value");
return success();
}
if (key == qsl("undoServerWrapWidth")) {
const int width = getVerifiedInt(L, __func__, 2, "value");
if (width < 20 || width > 500) {
return warnArgumentValue(L, __func__, qsl("width %1 is outside of the supported range of 20 to 500").arg(width));
}
host.mUndoServerWrapWidth = width;
return success();
}
if (key == qsl("askTlsAvailable")) {
host.mAskTlsAvailable = getVerifiedBool(L, __func__, 2, "value");
return success();
}
if (key == qsl("promptForMXPProcessorOn")) {
host.mPromptedForMXPProcessorOn = getVerifiedBool(L, __func__, 2, "value");
return success();
}
if (key == qsl("specialForceMXPProcessorOn")) {
host.setForceMXPProcessorOn(getVerifiedBool(L, __func__, 2, "value"));
return success();
}
if (key == qsl("promptForVersionInTTYPE")) {
host.mPromptedForVersionInTTYPE = getVerifiedBool(L, __func__, 2, "value");
return success();
}
if (key == qsl("versionInTTYPE")) {
host.mVersionInTTYPE = getVerifiedBool(L, __func__, 2, "value");
return success();
}
if (key == qsl("inputLineStrictUnixEndings")) {
host.mUSE_UNIX_EOL = getVerifiedBool(L, __func__, 2, "value");
return success();
}
if (key == qsl("autoClearInputLine")) {
host.mAutoClearCommandLineAfterSend = getVerifiedBool(L, __func__, 2, "value");
return success();
}
if (key == qsl("showSentText")) {
// Handle both legacy boolean and new string values
if (lua_isboolean(L, 2)) {
bool value = getVerifiedBool(L, __func__, 2, "value");
host.mCommandEchoMode = value ? Host::CommandEchoMode::ScriptControl : Host::CommandEchoMode::Never;
} else if (lua_isstring(L, 2)) {
QString value = getVerifiedString(L, __func__, 2, "value");
if (value == "never") {
host.mCommandEchoMode = Host::CommandEchoMode::Never;
} else if (value == "always") {
host.mCommandEchoMode = Host::CommandEchoMode::Always;
} else if (value == "script") {
host.mCommandEchoMode = Host::CommandEchoMode::ScriptControl;
} else {
lua_pushfstring(L, "setConfig: bad argument #2 value (expected 'never', 'always', or 'script', got '%s')", value.toUtf8().constData());
return warnArgumentValue(L, __func__, value);
}
} else {
lua_pushfstring(L, "setConfig: bad argument #2 type (expected boolean or string for 'showSentText', got %s)", luaL_typename(L, 2));
return warnArgumentValue(L, __func__, qsl("showSentText"));
}
return success();
}
if (key == qsl("fixUnnecessaryLinebreaks")) {
host.set_USE_IRE_DRIVER_BUGFIX(getVerifiedBool(L, __func__, 2, "value"));
return success();
}
if (key == qsl("specialForceCompressionOff")) {
host.mFORCE_NO_COMPRESSION = getVerifiedBool(L, __func__, 2, "value");
return success();
}
if (key == qsl("specialForceGAOff")) {
host.mFORCE_GA_OFF = getVerifiedBool(L, __func__, 2, "value");
return success();
}
if (key == qsl("specialForceCharsetNegotiationOff")) {
// specialForceCharsetNegotiationOff should not be used anymore, but we support it for compatibility
// it will be the inverse of enableCHARSET
host.mEnableCHARSET = !getVerifiedBool(L, __func__, 2, "value");
return success();
}
if (key == qsl("enableCHARSET")) {
host.mEnableCHARSET = getVerifiedBool(L, __func__, 2, "value");
return success();
}
if (key == qsl("forceNewEnvironNegotiationOff")) {
// forceNewEnvironNegotiationOff should not be used anymore, but we support it for compatibility
// it will be the inverse of enableNEWENVIRON
host.mEnableNEWENVIRON = !getVerifiedBool(L, __func__, 2, "value");
return success();
}
if (key == qsl("enableNEWENVIRON")) {
host.mEnableNEWENVIRON = getVerifiedBool(L, __func__, 2, "value");
return success();
}
if (key == qsl("compactInputLine")) {
const bool value = getVerifiedBool(L, __func__, 2, "value");
host.setCompactInputLine(value);
if (currentHost) {
mudlet::self()->dactionInputLine->setChecked(value);
}
return success();
}
if (key == qsl("editorAutoComplete")) {
host.mEditorAutoComplete = getVerifiedBool(L, __func__, 2, "value");
return success();
}
if (key == qsl("announceIncomingText")) {
host.mAnnounceIncomingText = getVerifiedBool(L, __func__, 2, "value");
return success();
}
if (key == qsl("advertiseScreenReader")) {
host.mAdvertiseScreenReader = getVerifiedBool(L, __func__, 2, "value");
return success();
}
if (key == qsl("enableClosedCaption")) {
host.mEnableClosedCaption = getVerifiedBool(L, __func__, 2, "value");
return success();
}
if (key == qsl("blankLinesBehaviour")) {
static const QStringList behaviours{"show", "hide", "replacewithspace"};
const auto behaviour = getVerifiedString(L, __func__, 2, "value");
if (!behaviours.contains(behaviour)) {
lua_pushnil(L);
lua_pushfstring(L, "invalid caretShortcut string \"%s\", it should be one of \"%s\"", lua_tostring(L, 2), behaviours.join(qsl("\", \"")).toUtf8().constData());
return 2;
}
if (behaviour == qsl("show")) {
host.mBlankLineBehaviour = Host::BlankLineBehaviour::Show;
} else if (behaviour == qsl("hide")) {
host.mBlankLineBehaviour = Host::BlankLineBehaviour::Hide;
} else if (behaviour == qsl("replacewithspace")) {
host.mBlankLineBehaviour = Host::BlankLineBehaviour::ReplaceWithSpace;
}
return success();
}
if (key == qsl("caretShortcut")) {
static const QStringList values{"none", "tab", "ctrltab", "f6"};
const auto value = getVerifiedString(L, __func__, 2, "value");
if (!values.contains(value)) {
lua_pushnil(L);
lua_pushfstring(L, "invalid caretShortcut string \"%s\", it should be one of \"%s\"", lua_tostring(L, 2), values.join(qsl("\", \"")).toUtf8().constData());
return 2;
}
if (value == qsl("tab")) {
host.mCaretShortcut = Host::CaretShortcut::Tab;
} else if (value == qsl("ctrltab")) {
host.mCaretShortcut = Host::CaretShortcut::CtrlTab;
} else if (value == qsl("f6")) {
host.mCaretShortcut = Host::CaretShortcut::F6;
} else {
host.mCaretShortcut = Host::CaretShortcut::None;
}
return success();
}
if (key == qsl("commandLineHistorySaveSize")) {
const auto value = getVerifiedInt(L, __func__, 2, "value");
host.setCommandLineHistorySaveSize(value);
return success();
}
if (key == qsl("controlCharacterHandling")) {
static const QStringList values{"asis", "oem", "picture"};
const auto value = getVerifiedString(L, __func__, 2, "value");
if (!values.contains(value)) {
lua_pushnil(L);
lua_pushfstring(L, "invalid commandLineHistorySaveSize string \"%s\", it should be one of \"%s\"", lua_tostring(L, 2), values.join(qsl("\", \"")).toUtf8().constData());
return 2;
}
if (value == qsl("oem")) {
host.setControlCharacterMode(ControlCharacterMode::OEM);
} else if (value == qsl("picture")) {
host.setControlCharacterMode(ControlCharacterMode::Picture);
} else {
host.setControlCharacterMode(ControlCharacterMode::AsIs);
}
return success();
}
if (key == qsl("logInHTML")) {
host.mIsNextLogFileInHtmlFormat = getVerifiedBool(L, __func__, 2, "value");
return success();
}
if (key == qsl("f3SearchEnabled")) {
const bool value = getVerifiedBool(L, __func__, 2, "value");
host.setF3SearchEnabled(value);
return success();
}
if (key == qsl("showTabConnectionIndicators")) {
mudlet::self()->setShowTabConnectionIndicators(getVerifiedBool(L, __func__, 2, "value"));
return success();
}
if (key == qsl("ambiguousEAsianWidthCharacters")) {
static const QStringList values{"narrow", "wide", "auto"};
const auto value = getVerifiedString(L, __func__, 2, "value");
if (!values.contains(value)) {
lua_pushnil(L);
lua_pushfstring(L, "invalid ambiguousEAsianWidthCharacters string \"%s\", it should be one of \"%s\"", lua_tostring(L, 2), values.join(qsl("\", \"")).toUtf8().constData());
return 2;
}
if (value == qsl("narrow")) {
host.setWideAmbiguousEAsianGlyphs(Qt::Unchecked);
} else if (value == qsl("wide")) {
host.setWideAmbiguousEAsianGlyphs(Qt::Checked);
} else {
host.setWideAmbiguousEAsianGlyphs(Qt::PartiallyChecked);
}
return success();
}
if (key == qsl("muteMediaAPI")) {
mudlet::self()->slot_muteAPI(getVerifiedBool(L, __func__, 2, "value"));
return success();
}
if (key == qsl("muteMediaGame")) {
mudlet::self()->slot_muteGame(getVerifiedBool(L, __func__, 2, "value"));
return success();
}
if (key == qsl("enableBlinkText")) {
host.setEnableBlinkText(getVerifiedBool(L, __func__, 2, "value"));
return success();
}
// Handle experiment keys
if (key.startsWith(qsl("experiment."))) {
const bool enabled = getVerifiedBool(L, __func__, 2, "value");
auto [result, errorMessage] = host.setExperimentEnabled(QString::fromUtf8(lua_tostring(L, 1)), enabled);
if (!result) {
return warnArgumentValue(L, __func__, errorMessage);
}
// Special handling for 3D mapper experiment
if (key == qsl("experiment.3dmap.modernmapper")) {
#if defined(INCLUDE_3DMAPPER)
if (host.mpMap && host.mpMap->mpMapper) {
host.mpMap->mpMapper->recreate3DWidget();
}
#endif
}
return success();
}
if (key == qsl("ircHostName")) {
QPair<bool, QString> result = dlgIRC::writeIrcHostName(&host, getVerifiedString(L, __func__, 2, "value"));
if (result.first) {
return success();
}
return warnArgumentValue(L, __func__, result.second);
}
if (key == qsl("ircHostPort")) {
QPair<bool, QString> result = dlgIRC::writeIrcHostPort(&host, getVerifiedInt(L, __func__, 2, "value"));
if (result.first) {
return success();
}
return warnArgumentValue(L, __func__, result.second);
}
if (key == qsl("ircHostSecure")) {
QPair<bool, QString> result = dlgIRC::writeIrcHostSecure(&host, getVerifiedBool(L, __func__, 2, "value"));
if (result.first) {
return success();
}
return warnArgumentValue(L, __func__, result.second);
}
if (key == qsl("ircChannels")) {
const QString channels = getVerifiedString(L, __func__, 2, "value");
QPair<bool, QString> result = dlgIRC::writeIrcChannels(&host, channels.split(qsl(" "), Qt::SkipEmptyParts));
if (result.first) {
return success();
}
return warnArgumentValue(L, __func__, result.second);
}
if (key == qsl("ircNickName")) {
QPair<bool, QString> result = dlgIRC::writeIrcNickName(&host, getVerifiedString(L, __func__, 2, "value"));
if (result.first) {
return success();
}
return warnArgumentValue(L, __func__, result.second);
}
if (key == qsl("ircPassword")) {
QPair<bool, QString> result = dlgIRC::writeIrcPassword(&host, getVerifiedString(L, __func__, 2, "value"));
if (result.first) {
return success();
}
return warnArgumentValue(L, __func__, result.second);
}
return warnArgumentValue(L, __func__, qsl("'%1' isn't a valid configuration option").arg(QString::fromUtf8(lua_tostring(L, 1))));
}
// Documentation: https://wiki.mudlet.org/w/Manual:Lua_Functions#announce
int TLuaInterpreter::announce(lua_State* L)
{
static const QStringList processingKinds{"importantall", "importantmostrecent", "all", "mostrecent", "currentthenmostrecent"};
const int n = lua_gettop(L);
if (!checkStringArg(L, __func__, 1, "text to announce") || (n > 1 && !checkStringArg(L, __func__, 2, "processing style"))) {
return lua_error(L);
}
// while this only has effect on Windows, it should fail silently in order not to spam
if (n > 1 && !processingKinds.contains(QLatin1StringView{lua_tostring(L, 2)})) {
lua_pushfstring(L, "%s: bad argument #%d type (processing should be one of %s, got %s!)", __func__, 2, processingKinds.join(qsl(", ")).toUtf8().constData(), lua_tostring(L, 2));
return lua_error(L);
}
mudlet::self()->announce(QString{lua_tostring(L, 1)}, (n > 1) ? QString{lua_tostring(L, 2)} : QString(), true);
return 0;
}
// Documentation: https://wiki.mudlet.org/w/Manual:Lua_Functions#getConfig
// Please use same options with same names in setConfig and getConfig and keep them in sync
// The no args case that returns a table is handled by getConfig in Other.lua
// that runs a loop with a list of these properties, please update that list.
int TLuaInterpreter::getConfig(lua_State* L)
{
auto& host = getHostFromLua(L);
const QString key = getVerifiedString(L, __func__, 1, "key");
if (key.isEmpty()) {
return warnArgumentValue(L, __func__, "you must provide a key");
}
// Check if second parameter requests string format (for enhanced API)
bool useStringFormat = false;
if (lua_gettop(L) >= 2 && lua_isboolean(L, 2)) {
useStringFormat = lua_toboolean(L, 2);
}
const std::unordered_map<QString, std::function<void()>> configMap = {
{qsl("mapRoomSize"),
[&]() {
lua_pushnumber(L, host.mRoomSize);
}},
{qsl("mapExitSize"),
[&]() {
lua_pushnumber(L, host.mLineSize);
}},
{qsl("mapRoundRooms"),
[&]() {
lua_pushboolean(L, host.mBubbleMode);
}},
{qsl("showRoomIdsOnMap"),
[&]() {
lua_pushboolean(L, host.mShowRoomID);
}},
{qsl("show3dMapView"),
[&]() {
#if defined(INCLUDE_3DMAPPER)
if (host.mpMap && host.mpMap->mpMapper) {
auto widget = host.mpMap->mpMapper->glWidget;
lua_pushboolean(L, (widget && widget->isVisible()));
return;
}
#endif
lua_pushboolean(L, false);
}},
{qsl("mapperPanelVisible"),
[&]() {
lua_pushboolean(L, host.mShowPanel);
}},
{qsl("mapShowRoomBorders"),
[&]() {
lua_pushboolean(L, host.mMapperShowRoomBorders);
}},
{qsl("mapInfoColor"),
[&]() {
lua_newtable(L);
lua_pushnumber(L, host.mMapInfoBg.red());
lua_rawseti(L, -2, 1);
lua_pushnumber(L, host.mMapInfoBg.green());
lua_rawseti(L, -2, 2);
lua_pushnumber(L, host.mMapInfoBg.blue());
lua_rawseti(L, -2, 3);
lua_pushnumber(L, host.mMapInfoBg.alpha());
lua_rawseti(L, -2, 4);
}},
{qsl("mapShowGrid"),
[&]() {
lua_pushboolean(L, host.mMapperShowGrid);
}},
{qsl("editorAutoComplete"),
[&]() {
lua_pushboolean(L, host.mEditorAutoComplete);
}},
{qsl("enableGMCP"),
[&]() {
lua_pushboolean(L, host.mEnableGMCP);
}},
{qsl("enableMSSP"),
[&]() {
lua_pushboolean(L, host.mEnableMSSP);
}},
{qsl("enableMSDP"),
[&]() {
lua_pushboolean(L, host.mEnableMSDP);
}},
{qsl("enableMSP"),
[&]() {
lua_pushboolean(L, host.mEnableMSP);
}},
{qsl("enableMTTS"),
[&]() {
lua_pushboolean(L, host.mEnableMTTS);
}},
{qsl("enableMNES"),
[&]() {
lua_pushboolean(L, host.mEnableMNES);
}},
{qsl("enableMXP"),
[&]() {
lua_pushboolean(L, host.mEnableMXP);
}},
{qsl("enableNAWS"),
[&]() {
lua_pushboolean(L, host.mEnableNAWS);
}},
{qsl("undoServerWrap"),
[&]() {
lua_pushboolean(L, host.mUndoServerWrap);
}},
{qsl("undoServerWrapWidth"),
[&]() {
lua_pushnumber(L, host.mUndoServerWrapWidth);
}},
{qsl("logDirectory"),
[&]() {
const auto logDir = host.mLogDir;
if (logDir == nullptr || logDir.isEmpty()) {
lua_pushstring(L, mudlet::getMudletPath(enums::profileReplayAndLogFilesPath, getHostFromLua(L).getName()).toUtf8().constData());
} else {
lua_pushstring(L, host.mLogDir.toUtf8().constData());
}
}},
{qsl("askTlsAvailable"),
[&]() {
lua_pushboolean(L, host.mAskTlsAvailable);
}},
{qsl("promptForMXPProcessorOn"),
[&]() {
lua_pushboolean(L, host.mPromptedForMXPProcessorOn);
}},
{qsl("specialForceMXPProcessorOn"),
[&]() {
lua_pushboolean(L, host.getForceMXPProcessorOn());
}},
{qsl("promptForVersionInTTYPE"),
[&]() {
lua_pushboolean(L, host.mPromptedForVersionInTTYPE);
}},
{qsl("versionInTTYPE"),
[&]() {
lua_pushboolean(L, host.mVersionInTTYPE);
}},
{qsl("inputLineStrictUnixEndings"),
[&]() {
lua_pushboolean(L, host.mUSE_UNIX_EOL);
}},
{qsl("autoClearInputLine"),
[&]() {
lua_pushboolean(L, host.mAutoClearCommandLineAfterSend);
}},
{qsl("showSentText"),
[&]() {
if (useStringFormat) {
// Return string representation for new enhanced API
switch (host.mCommandEchoMode) {
case Host::CommandEchoMode::Never:
lua_pushstring(L, "never");
break;
case Host::CommandEchoMode::Always:
lua_pushstring(L, "always");
break;
case Host::CommandEchoMode::ScriptControl:
lua_pushstring(L, "script");
break;
}
} else {
// For backward compatibility, return boolean for legacy scripts
switch (host.mCommandEchoMode) {
case Host::CommandEchoMode::Never:
lua_pushboolean(L, false);
break;
case Host::CommandEchoMode::Always:
case Host::CommandEchoMode::ScriptControl:
// Both modes map to true for backward compatibility
lua_pushboolean(L, true);
break;
}
}
}},
{qsl("fixUnnecessaryLinebreaks"),
[&]() {
lua_pushboolean(L, host.mUSE_IRE_DRIVER_BUGFIX);
}},
{qsl("specialForceCompressionOff"),
[&]() {
lua_pushboolean(L, host.mFORCE_NO_COMPRESSION);
}},
{qsl("specialForceGAOff"),
[&]() {
lua_pushboolean(L, host.mFORCE_GA_OFF);
}},
{qsl("specialForceMxpNegotiationOff"),
[&]() {
// specialForceMxpNegotiationOff should not be used anymore, but we support it for compatibility
// it will be the inverse of enableMXP
lua_pushboolean(L, !host.mEnableMXP);
}},
{qsl("specialForceCharsetNegotiationOff"),
[&]() {
// specialForceCharsetNegotiationOff should not be used anymore, but we support it for compatibility
// it will be the inverse of enableCHARSET
lua_pushboolean(L, !host.mEnableCHARSET);
}},
{qsl("enableCHARSET"),
[&]() {
lua_pushboolean(L, host.mEnableCHARSET);
}},
{qsl("forceNewEnvironNegotiationOff"),
[&]() {
// forceNewEnvironNegotiationOff should not be used anymore, but we support it for compatibility
// it will be the inverse of enableNEWENVIRON
lua_pushboolean(L, !host.mEnableNEWENVIRON);
}},
{qsl("enableNEWENVIRON"),
[&]() {
lua_pushboolean(L, host.mEnableNEWENVIRON);
}},
{qsl("compactInputLine"),
[&]() {
lua_pushboolean(L, host.getCompactInputLine());
}},
{qsl("announceIncomingText"),
[&]() {
lua_pushboolean(L, host.mAnnounceIncomingText);
}},
{qsl("blankLinesBehaviour"),
[&]() {
const auto behaviour = host.mBlankLineBehaviour;
if (behaviour == Host::BlankLineBehaviour::Show) {
lua_pushstring(L, "show");
} else if (behaviour == Host::BlankLineBehaviour::Hide) {
lua_pushstring(L, "hide");
} else if (behaviour == Host::BlankLineBehaviour::ReplaceWithSpace) {
lua_pushstring(L, "replacewithspace");
}
}},
{qsl("caretShortcut"),
[&]() {
const auto caret = host.mCaretShortcut;
if (caret == Host::CaretShortcut::None) {
lua_pushstring(L, "none");
} else if (caret == Host::CaretShortcut::Tab) {
lua_pushstring(L, "tab");
} else if (caret == Host::CaretShortcut::CtrlTab) {
lua_pushstring(L, "ctrltab");
} else if (caret == Host::CaretShortcut::F6) {
lua_pushstring(L, "f6");
}
}},
{qsl("commandLineHistorySaveSize"),
[&]() {
lua_pushnumber(L, host.getCommandLineHistorySaveSize());
}},
{qsl("controlCharacterHandling"),
[&]() {
const auto controlCharacterMode = host.getControlCharacterMode();
switch (controlCharacterMode) {
case ControlCharacterMode::Picture:
lua_pushstring(L, "picture");
break;
case ControlCharacterMode::OEM:
lua_pushstring(L, "oem");
break;
default:
lua_pushstring(L, "asis");
}
}},
{qsl("logInHTML"),
[&]() {
lua_pushboolean(L, host.mIsNextLogFileInHtmlFormat);
}},
{qsl("f3SearchEnabled"),
[&]() {
lua_pushboolean(L, host.getF3SearchEnabled());
}},
{qsl("showTabConnectionIndicators"),
[&]() {
lua_pushboolean(L, mudlet::self()->mShowTabConnectionIndicators);
}},
{qsl("advertiseScreenReader"),
[&]() {
lua_pushboolean(L, host.mAdvertiseScreenReader);
}},
{qsl("ambiguousEAsianWidthCharacters"),
[&]() {
const auto ambiguousEAsianGlyphWidth = host.getWideAmbiguousEAsianGlyphsControlState();
switch (ambiguousEAsianGlyphWidth) {
case Qt::Unchecked:
lua_pushstring(L, "narrow");
break;
case Qt::Checked:
lua_pushstring(L, "wide");
break;
default:
lua_pushstring(L, "auto");
}
}},
{qsl("enableClosedCaption"),
[&]() {
lua_pushboolean(L, host.mEnableClosedCaption);
}},
{qsl("enableBlinkText"),
[&]() {
lua_pushboolean(L, host.getEnableBlinkText());
}},
{qsl("showUpperLowerLevels"),
[&]() {
lua_pushboolean(L, mudlet::self()->mDrawUpperLowerLevels);
}},
{qsl("muteMediaAPI"),
[&]() {
lua_pushboolean(L, mudlet::self()->muteAPI());
}},
{qsl("muteMediaGame"),
[&]() {
lua_pushboolean(L, mudlet::self()->muteGame());
}},
{qsl("ircHostName"),
[&]() {
lua_pushstring(L, dlgIRC::readIrcHostName(&host).toUtf8().constData());
}},
{qsl("ircHostPort"),
[&]() {
lua_pushnumber(L, dlgIRC::readIrcHostPort(&host));
}},
{qsl("ircHostSecure"),
[&]() {
lua_pushboolean(L, dlgIRC::readIrcHostSecure(&host));
}},
{qsl("ircChannels"),
[&]() {
lua_pushstring(L, dlgIRC::readIrcChannels(&host).join(qsl(" ")).toUtf8().constData());
}},
{qsl("ircNickName"),
[&]() {
lua_pushstring(L, dlgIRC::readIrcNickName(&host).toUtf8().constData());
}},
{qsl("ircPassword"), [&]() {
lua_pushstring(L, dlgIRC::readIrcPassword(&host).toUtf8().constData());
}}};
auto it = configMap.find(key);
if (it != configMap.end()) {
it->second();
return 1;
}
// Handle experiment keys
if (key.startsWith(qsl("experiment."))) {
if (key == qsl("experiment.list")) {
// Special case: return list of all valid experiments
QStringList validExperiments = host.getValidExperiments();
lua_createtable(L, validExperiments.size(), 0);
for (int i = 0; i < validExperiments.size(); ++i) {
lua_pushstring(L, validExperiments.at(i).toUtf8().constData());
lua_rawseti(L, -2, i + 1);
}
} else if (key.endsWith(qsl(".active"))) {
// Handle special case: experiment.<group>.active returns active experiment name
QString group = key.left(key.length() - 7); // Remove ".active"
QString activeExperiment = host.getActiveExperimentInGroup(group);
if (activeExperiment.isEmpty()) {
lua_pushnil(L);
} else {
lua_pushstring(L, activeExperiment.toUtf8().constData());
}
} else {
// Regular experiment key - but only if it's valid
QStringList validExperiments = host.getValidExperiments();
if (validExperiments.contains(key)) {
lua_pushboolean(L, host.experimentEnabled(key));
} else {
lua_pushboolean(L, false); // Invalid experiments are always false
}
}
return 1;
}
return warnArgumentValue(L, __func__, qsl("'%1' isn't a valid configuration option").arg(key));
}
// Documentation: https://wiki.mudlet.org/w/Manual:Lua_Functions#getSaveCommandHistory
int TLuaInterpreter::getSaveCommandHistory(lua_State* L)
{
auto& host = getHostFromLua(L);
auto numberOfLines = host.getCommandLineHistorySaveSize();
if (!numberOfLines) {
// We do not use warnArgumentValue(...) because it is valid to have
// this disabled and we do not want a message to be painted on the
// Central Debug Console:
lua_pushboolean(L, false);
lua_pushstring(L, "disabled by profile global preference");
return 2;
}
const char* name = "main";
if (lua_gettop(L)) {
name = CMDLINE_NAME(L, 1);
}
auto pCommandline = COMMANDLINE(L, QString{name});
lua_pushboolean(L, pCommandline->mSaveCommands);
lua_pushstring(L, (pCommandline->mSaveCommands ? qsl("enabled (%1 lines will be saved)").arg(QString::number(numberOfLines)) : qsl("disabled")).toUtf8().constData());
return 2;
}
// Documentation: https://wiki.mudlet.org/w/Manual:Lua_Functions#setSaveCommandHistory
int TLuaInterpreter::setSaveCommandHistory(lua_State* L)
{
auto n = lua_gettop(L);
auto& host = getHostFromLua(L);
auto numberOfLines = host.getCommandLineHistorySaveSize();
if (!numberOfLines) {
// Unlike for the getter we do want to alert on trying to set the
// per-commandLine option when things are disabled globally for the
// profile:
return warnArgumentValue(L, __func__, "disabled by profile global preference");
}
// both defaults have to stand outside the argument handling below:
// setSaveCommandHistory() and setSaveCommandHistory(name) each turn saving
// on, so neither belongs inside a branch on the argument count:
const char* name = "main";
bool saveCommands = true;
if (n > 0) {
if (lua_type(L, 1) == LUA_TSTRING) {
// First argument is a string so is presumably a command line name
name = CMDLINE_NAME(L, 1);
if (n > 1) {
saveCommands = getVerifiedBool(L, __func__, 2, "save command history", true);
}
} else {
if (lua_type(L, 1) != LUA_TBOOLEAN) {
lua_pushfstring(L, "%s: bad argument #1 type (command line name as string or save history as boolean is optional, got %s!)", __func__, luaL_typename(L, 1));
return lua_error(L); // Dummy return!
}
saveCommands = getVerifiedBool(L, __func__, 1, "save command history", true);
}
}
auto pCommandline = COMMANDLINE(L, QString{name});
pCommandline->mSaveCommands = saveCommands;
lua_pushboolean(L, true);
return 1;
}
void TLuaInterpreter::updateEditor()
{
if (mpHost->mpEditorDialog) {
mpHost->mpEditorDialog->mNeedUpdateData = true;
}
}