mirror of
https://github.com/Mudlet/Mudlet
synced 2026-08-13 18:26:27 -04:00
#### 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.
8708 lines
373 KiB
C++
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;
|
|
}
|
|
}
|