/* FCE Ultra - NES/Famicom Emulator * * Copyright notice for this file: * Copyright (C) 2020 mjbudd77 * * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ // LuaControl.cpp // #include #include #include #ifdef WIN32 #include #endif #include #include #include #include #include "../../fceu.h" #ifdef _S9XLUA_H #include "../../fceulua.h" #endif #include "common/os_utils.h" #include "Qt/LuaControl.h" #include "Qt/main.h" #include "Qt/input.h" #include "Qt/config.h" #include "Qt/keyscan.h" #include "Qt/fceuWrapper.h" #include "Qt/ConsoleUtilities.h" static bool luaScriptRunning = false; static bool updateLuaDisplay = false; static bool openLuaKillMsgBox = false; static int luaKillMsgBoxRetVal = 0; struct luaConsoleOutputBuffer { int head; int tail; int size; char *buf; luaConsoleOutputBuffer(void) { tail = head = 0; size = 4096; buf = (char *)malloc(size); } ~luaConsoleOutputBuffer(void) { if (buf) { free(buf); buf = NULL; } } void addLine(const char *l) { int i = 0; //printf("Adding Line %i: '%s'\n", head, l ); while (l[i] != 0) { buf[head] = l[i]; i++; head = (head + 1) % size; if (head == tail) { tail = (tail + 1) % size; } } } void clear(void) { tail = head = 0; } }; static luaConsoleOutputBuffer outBuf; static std::list winList; static void updateLuaWindows(void); //---------------------------------------------------- LuaControlDialog_t::LuaControlDialog_t(QWidget *parent) : QDialog(parent, Qt::Window) { QVBoxLayout *mainLayout; QHBoxLayout *hbox; QPushButton *closeButton; QLabel *lbl; std::string filename; QSettings settings; resize(512, 512); setWindowTitle(tr("Lua Script Control")); mainLayout = new QVBoxLayout(); lbl = new QLabel(tr("Script File:")); scriptPath = new QLineEdit(); scriptArgs = new QLineEdit(); g_config->getOption("SDL.LastLoadLua", &filename); scriptPath->setText( tr(filename.c_str()) ); scriptPath->setClearButtonEnabled(true); scriptArgs->setClearButtonEnabled(true); luaOutput = new QTextEdit(); luaOutput->setReadOnly(true); hbox = new QHBoxLayout(); browseButton = new QPushButton(tr("Browse")); stopButton = new QPushButton(tr("Stop")); if (luaScriptRunning) { startButton = new QPushButton(tr("Restart")); } else { startButton = new QPushButton(tr("Start")); } stopButton->setEnabled(luaScriptRunning); connect(browseButton, SIGNAL(clicked()), this, SLOT(openLuaScriptFile(void))); connect(stopButton, SIGNAL(clicked()), this, SLOT(stopLuaScript(void))); connect(startButton, SIGNAL(clicked()), this, SLOT(startLuaScript(void))); hbox->addWidget(browseButton); hbox->addWidget(stopButton); hbox->addWidget(startButton); mainLayout->addWidget(lbl); mainLayout->addWidget(scriptPath); mainLayout->addLayout(hbox); hbox = new QHBoxLayout(); lbl = new QLabel(tr("Arguments:")); hbox->addWidget(lbl); hbox->addWidget(scriptArgs); mainLayout->addLayout(hbox); lbl = new QLabel(tr("Output Console:")); mainLayout->addWidget(lbl); mainLayout->addWidget(luaOutput); closeButton = new QPushButton( tr("Close") ); closeButton->setIcon(style()->standardIcon(QStyle::SP_DialogCloseButton)); connect(closeButton, SIGNAL(clicked(void)), this, SLOT(closeWindow(void))); hbox = new QHBoxLayout(); hbox->addStretch(5); hbox->addWidget( closeButton, 1 ); mainLayout->addLayout( hbox ); setLayout(mainLayout); winList.push_back(this); periodicTimer = new QTimer(this); connect(periodicTimer, &QTimer::timeout, this, &LuaControlDialog_t::updatePeriodic); periodicTimer->start(200); // 5hz restoreGeometry(settings.value("LuaWindow/geometry").toByteArray()); } //---------------------------------------------------- LuaControlDialog_t::~LuaControlDialog_t(void) { QSettings settings; std::list::iterator it; //printf("Destroy Lua Control Window\n"); periodicTimer->stop(); for (it = winList.begin(); it != winList.end(); it++) { if ((*it) == this) { winList.erase(it); //printf("Removing Lua Window\n"); break; } } settings.setValue("LuaWindow/geometry", saveGeometry()); } //---------------------------------------------------- void LuaControlDialog_t::closeEvent(QCloseEvent *event) { //printf("Lua Control Close Window Event\n"); done(0); deleteLater(); event->accept(); } //---------------------------------------------------- void LuaControlDialog_t::closeWindow(void) { //printf("Lua Control Close Window\n"); done(0); deleteLater(); } //---------------------------------------------------- void LuaControlDialog_t::updatePeriodic(void) { //printf("Update Lua\n"); if (updateLuaDisplay) { updateLuaWindows(); updateLuaDisplay = false; } if (openLuaKillMsgBox) { openLuaKillMessageBox(); openLuaKillMsgBox = false; } } //---------------------------------------------------- void LuaControlDialog_t::openLuaKillMessageBox(void) { int ret; QMessageBox msgBox(this); luaKillMsgBoxRetVal = 0; msgBox.setIcon(QMessageBox::Warning); msgBox.setText(tr("The Lua script running has been running a long time.\nIt may have gone crazy. Kill it? (I won't ask again if you say No)\n")); msgBox.setStandardButtons(QMessageBox::Yes); msgBox.addButton(QMessageBox::No); msgBox.setDefaultButton(QMessageBox::No); ret = msgBox.exec(); if (ret == QMessageBox::Yes) { luaKillMsgBoxRetVal = 1; } } //---------------------------------------------------- void LuaControlDialog_t::openLuaScriptFile(void) { #ifdef _S9XLUA_H int ret, useNativeFileDialogVal; QString filename; std::string last; char dir[2048]; char exePath[2048]; const char *luaPath; QFileDialog dialog(this, tr("Open LUA Script")); QList urls; QDir d; fceuExecutablePath(exePath, sizeof(exePath)); //urls = dialog.sidebarUrls(); urls << QUrl::fromLocalFile(QDir::rootPath()); urls << QUrl::fromLocalFile(QStandardPaths::standardLocations(QStandardPaths::HomeLocation).first()); urls << QUrl::fromLocalFile(QStandardPaths::standardLocations(QStandardPaths::DesktopLocation).first()); urls << QUrl::fromLocalFile(QStandardPaths::standardLocations(QStandardPaths::DownloadLocation).first()); urls << QUrl::fromLocalFile(QDir(FCEUI_GetBaseDirectory()).absolutePath()); if (exePath[0] != 0) { d.setPath(QString(exePath) + "/../luaScripts"); if (d.exists()) { urls << QUrl::fromLocalFile(d.absolutePath()); } } #ifndef WIN32 d.setPath("/usr/share/fceux/luaScripts"); if (d.exists()) { urls << QUrl::fromLocalFile(d.absolutePath()); } #endif luaPath = getenv("LUA_PATH"); // Parse LUA_PATH and add to urls if (luaPath) { int i, j; char stmp[2048]; i = j = 0; while (luaPath[i] != 0) { if (luaPath[i] == ';') { stmp[j] = 0; if (j > 0) { d.setPath(stmp); if (d.exists()) { urls << QUrl::fromLocalFile(d.absolutePath()); } } j = 0; } else { stmp[j] = luaPath[i]; j++; } i++; } stmp[j] = 0; if (j > 0) { d.setPath(stmp); if (d.exists()) { urls << QUrl::fromLocalFile(d.absolutePath()); } } } dialog.setFileMode(QFileDialog::ExistingFile); dialog.setNameFilter(tr("LUA Scripts (*.lua *.LUA) ;; All files (*)")); dialog.setViewMode(QFileDialog::List); dialog.setFilter(QDir::AllEntries | QDir::AllDirs | QDir::Hidden); dialog.setLabelText(QFileDialog::Accept, tr("Load")); g_config->getOption("SDL.LastLoadLua", &last); if (last.size() == 0) { #ifdef WIN32 last.assign(FCEUI_GetBaseDirectory()); #else last.assign("/usr/share/fceux/luaScripts"); #endif } getDirFromFile(last.c_str(), dir, sizeof(dir)); dialog.setDirectory(tr(dir)); // Check config option to use native file dialog or not g_config->getOption("SDL.UseNativeFileDialog", &useNativeFileDialogVal); dialog.setOption(QFileDialog::DontUseNativeDialog, !useNativeFileDialogVal); dialog.setSidebarUrls(urls); ret = dialog.exec(); if (ret) { QStringList fileList; fileList = dialog.selectedFiles(); if (fileList.size() > 0) { filename = fileList[0]; } } if (filename.isNull()) { return; } qDebug() << "selected file path : " << filename.toUtf8(); g_config->setOption("SDL.LastLoadLua", filename.toStdString().c_str()); scriptPath->setText(filename); #endif } //---------------------------------------------------- void LuaControlDialog_t::startLuaScript(void) { #ifdef _S9XLUA_H outBuf.clear(); FCEU_WRAPPER_LOCK(); if (0 == FCEU_LoadLuaCode(scriptPath->text().toStdString().c_str(), scriptArgs->text().toStdString().c_str())) { char error_msg[2048]; sprintf( error_msg, "Error: Could not open the selected lua script: '%s'\n", scriptPath->text().toStdString().c_str()); FCEUD_PrintError(error_msg); } FCEU_WRAPPER_UNLOCK(); #endif } //---------------------------------------------------- void LuaControlDialog_t::stopLuaScript(void) { #ifdef _S9XLUA_H FCEU_WRAPPER_LOCK(); FCEU_LuaStop(); FCEU_WRAPPER_UNLOCK(); #endif } //---------------------------------------------------- void LuaControlDialog_t::refreshState(void) { int i; std::string luaOutputText; if (luaScriptRunning) { stopButton->setEnabled(true); startButton->setText(tr("Restart")); } else { stopButton->setEnabled(false); startButton->setText(tr("Start")); } i = outBuf.tail; while (i != outBuf.head) { luaOutputText.append(1, outBuf.buf[i]); i = (i + 1) % outBuf.size; } luaOutput->setText(luaOutputText.c_str()); luaOutput->moveCursor(QTextCursor::End); } //---------------------------------------------------- static void updateLuaWindows(void) { std::list::iterator it; for (it = winList.begin(); it != winList.end(); it++) { (*it)->refreshState(); } } //---------------------------------------------------- void WinLuaOnStart(intptr_t hDlgAsInt) { luaScriptRunning = true; //printf("Lua Script Running: %i \n", luaScriptRunning ); updateLuaDisplay = true; } //---------------------------------------------------- void WinLuaOnStop(intptr_t hDlgAsInt) { luaScriptRunning = false; //printf("Lua Script Running: %i \n", luaScriptRunning ); updateLuaDisplay = true; } //---------------------------------------------------- void PrintToWindowConsole(intptr_t hDlgAsInt, const char *str) { //printf("%s\n", str ); outBuf.addLine(str); updateLuaDisplay = true; } //---------------------------------------------------- #ifdef WIN32 int LuaPrintfToWindowConsole(_In_z_ _Printf_format_string_ const char *format, ...) #else int LuaPrintfToWindowConsole(const char *__restrict format, ...) throw() #endif { int retval; va_list args; char msg[2048]; va_start(args, format); retval = ::vsnprintf(msg, sizeof(msg), format, args); va_end(args); msg[sizeof(msg) - 1] = 0; outBuf.addLine(msg); updateLuaDisplay = true; return (retval); }; //---------------------------------------------------- int LuaKillMessageBox(void) { //printf("Kill Lua Prompted\n"); luaKillMsgBoxRetVal = 0; openLuaKillMsgBox = true; while (openLuaKillMsgBox) { msleep(100); } return luaKillMsgBoxRetVal; } //----------------------------------------------------