For Qt GUI, added initial framework for use of the Qt built-in ECMAScript (javascript based) engine. This scripting capability will allow users to load their own custom UI windows (created using Qt Creator tool) in the GUI. This is intended to serve as a the Qt GUI's functional replacement for building GUI elements using IUP in LUA scripts. This is a work in progress.

This commit is contained in:
harry 2024-01-14 08:58:10 -05:00
parent b53d087fca
commit 3436e221de
10 changed files with 994 additions and 35 deletions

View File

@ -10,34 +10,80 @@ if (${PUBLIC_RELEASE})
endif()
if ( ${QT6} )
message( STATUS "GUI Frontend: Qt6")
set( Qt Qt6 )
else()
message( STATUS "GUI Frontend: Qt5")
set( Qt Qt5 )
set( QT 6 )
endif()
if ( ${QHELP} )
set(QtHelpModule Help)
add_definitions( -D_USE_QHELP )
if (NOT DEFINED QT)
message( STATUS "Attempting to determine Qt Version...")
find_package( Qt6 COMPONENTS Core)
if (${Qt6Core_FOUND})
message( STATUS "Found Qt Version: ${Qt6Core_VERSION}")
set( QT 6 )
else()
find_package( Qt5 COMPONENTS Core)
if (${Qt5Core_FOUND})
message( STATUS "Found Qt Version: ${Qt5Core_VERSION}")
set( QT 5 )
endif()
endif()
endif()
if ( ${FCEU_PROFILER_ENABLE} )
message( STATUS "FCEU Profiler Enabled")
add_definitions( -D__FCEU_PROFILER_ENABLE__ )
endif()
if ( ${QT6} )
find_package( Qt6 REQUIRED COMPONENTS Widgets OpenGL OpenGLWidgets ${QtHelpModule})
add_definitions( ${Qt6Widgets_DEFINITIONS} ${Qt6Help_DEFINITIONS} ${Qt6OpenGLWidgets_DEFINITIONS} )
include_directories( ${Qt6Widgets_INCLUDE_DIRS} ${Qt6Help_INCLUDE_DIRS} ${Qt6OpenGLWidgets_INCLUDE_DIRS} )
if ( ${QT} EQUAL 6 )
message( STATUS "GUI Frontend: Qt6")
set( Qt Qt6 )
find_package( Qt6 REQUIRED COMPONENTS Widgets OpenGL OpenGLWidgets)
find_package( Qt6 COMPONENTS Help QUIET)
find_package( Qt6 COMPONENTS Qml)
add_definitions( ${Qt6Widgets_DEFINITIONS} ${Qt6Qml_DEFINITIONS} ${Qt6Help_DEFINITIONS} ${Qt6OpenGLWidgets_DEFINITIONS} )
include_directories( ${Qt6Widgets_INCLUDE_DIRS} ${Qt6Qml_INCLUDE_DIRS} ${Qt6Help_INCLUDE_DIRS} ${Qt6OpenGLWidgets_INCLUDE_DIRS} )
if (${Qt6Help_FOUND})
message( STATUS "Qt6 Help Module Found")
if (${QHELP})
add_definitions( -D_USE_QHELP )
endif()
else()
message( STATUS "Qt6 Help Module Not Found")
endif()
if (${Qt6Qml_FOUND})
message( STATUS "Qt6 Qml Module Found")
#add_definitions( -D__FCEU_QSCRIPT_ENABLE__ )
else()
message( STATUS "Qt6 Qml Module Not Found")
endif()
else()
find_package( Qt5 REQUIRED COMPONENTS Widgets OpenGL ${QtHelpModule})
add_definitions( ${Qt5Widgets_DEFINITIONS} ${Qt5Help_DEFINITIONS} )
include_directories( ${Qt5Widgets_INCLUDE_DIRS} ${Qt5Help_INCLUDE_DIRS} )
endif()
message( STATUS "GUI Frontend: Qt5")
set( Qt Qt5 )
find_package( Qt5 REQUIRED COMPONENTS Widgets OpenGL)
find_package( Qt5 COMPONENTS Help QUIET)
find_package( Qt5 COMPONENTS Qml)
add_definitions( ${Qt5Widgets_DEFINITIONS} ${Qt5Qml_DEFINITIONS} ${Qt5Help_DEFINITIONS} )
include_directories( ${Qt5Widgets_INCLUDE_DIRS} ${Qt5Qml_INCLUDE_DIRS} ${Qt5Help_INCLUDE_DIRS} )
if (${Qt5Help_FOUND})
message( STATUS "Qt5 Help Module Found")
if (${QHELP})
add_definitions( -D_USE_QHELP )
endif()
else()
message( STATUS "Qt5 Help Module Not Found")
endif()
if (${Qt5Qml_FOUND})
message( STATUS "Qt5 Qml Module Found")
add_definitions( -D__FCEU_QSCRIPT_ENABLE__ )
else()
message( STATUS "Qt5 Qml Module Not Found")
endif()
endif()
if(WIN32)
find_package(OpenGL REQUIRED)
@ -579,6 +625,7 @@ set(SRC_DRIVERS_SDL
${CMAKE_CURRENT_SOURCE_DIR}/drivers/Qt/ConsoleSoundConf.cpp
${CMAKE_CURRENT_SOURCE_DIR}/drivers/Qt/StateRecorderConf.cpp
${CMAKE_CURRENT_SOURCE_DIR}/drivers/Qt/iNesHeaderEditor.cpp
${CMAKE_CURRENT_SOURCE_DIR}/drivers/Qt/QtScriptManager.cpp
${CMAKE_CURRENT_SOURCE_DIR}/drivers/Qt/SplashScreen.cpp
${CMAKE_CURRENT_SOURCE_DIR}/drivers/Qt/TraceLogger.cpp
${CMAKE_CURRENT_SOURCE_DIR}/drivers/Qt/AboutWindow.cpp
@ -666,6 +713,7 @@ target_link_libraries( ${APP_NAME}
${ASAN_LDFLAGS} ${GPROF_LDFLAGS}
${${Qt}Widgets_LIBRARIES}
${${Qt}Help_LIBRARIES}
${${Qt}Qml_LIBRARIES}
${${Qt}OpenGL_LIBRARIES}
${${Qt}OpenGLWidgets_LIBRARIES}
${OPENGL_LDFLAGS}

View File

@ -67,6 +67,7 @@
#include "Qt/main.h"
#include "Qt/dface.h"
#include "Qt/input.h"
#include "Qt/throttle.h"
#include "Qt/ColorMenu.h"
#include "Qt/ConsoleWindow.h"
#include "Qt/InputConf.h"
@ -86,6 +87,7 @@
#include "Qt/TimingConf.h"
#include "Qt/FrameTimingStats.h"
#include "Qt/LuaControl.h"
#include "Qt/QtScriptManager.h"
#include "Qt/CheatsConf.h"
#include "Qt/GameGenie.h"
#include "Qt/HexEditor.h"
@ -270,6 +272,9 @@ consoleWin_t::consoleWin_t(QWidget *parent)
// Create AVI Recording Disk Thread
aviDiskThread = new AviRecordDiskThread_t(this);
#ifdef __FCEU_QSCRIPT_ENABLE__
QtScriptManager::create(this);
#endif
scrHandlerConnected = false;
}
@ -1079,7 +1084,7 @@ void consoleWin_t::createMainMenu(void)
connect( Hotkeys[ HK_SELECT_STATE_NEXT ].getShortcut(), SIGNAL(activated()), this, SLOT(incrementState(void)) );
#ifdef _S9XLUA_H
// File -> Quick Save
// File -> Load Lua
loadLuaAct = new QAction(tr("Load &Lua Script"), this);
//loadLuaAct->setShortcut( QKeySequence(tr("F5")));
loadLuaAct->setStatusTip(tr("Load Lua Script"));
@ -1090,7 +1095,20 @@ void consoleWin_t::createMainMenu(void)
fileMenu->addSeparator();
#else
loadLuaAct = NULL;
loadLuaAct = nullptr;
#endif
#ifdef _S9XLUA_H
// File -> Load QScript
loadJsAct = new QAction(tr("Load &Qt Script"), this);
loadJsAct->setStatusTip(tr("Load Qt Script"));
connect(loadJsAct, SIGNAL(triggered()), this, SLOT(loadJs(void)) );
fileMenu->addAction(loadJsAct);
fileMenu->addSeparator();
#else
loadJsAct = NULL;
#endif
// File -> Screenshot
@ -2981,6 +2999,19 @@ void consoleWin_t::loadLua(void)
#endif
}
void consoleWin_t::loadJs(void)
{
#ifdef __FCEU_QSCRIPT_ENABLE__
QScriptDialog_t *jsCtrlWin;
//printf("Open JS Control Window\n");
jsCtrlWin = new QScriptDialog_t(this);
jsCtrlWin->show();
#endif
}
void consoleWin_t::openInputConfWin(void)
{
//printf("Open Input Config Window\n");
@ -4546,21 +4577,30 @@ void consoleWin_t::emuFrameFinish(void)
{
static bool eventProcessingInProg = false;
if ( eventProcessingInProg )
{ // Prevent recursion as processEvents function can double back on us
return;
}
eventProcessingInProg = true;
// Process all events before attempting to render viewport
QCoreApplication::processEvents();
guiSignalRecvMark();
eventProcessingInProg = false;
//if ( eventProcessingInProg )
//{ // Prevent recursion as processEvents function can double back on us
// return;
//}
// Prevent recursion as processEvents function can double back on us
if ( !eventProcessingInProg )
{
eventProcessingInProg = true;
// Process all events before attempting to render viewport
QCoreApplication::processEvents();
eventProcessingInProg = false;
}
// Update Input Devices
FCEUD_UpdateInput();
//printf("EMU Frame Finish\n");
#ifdef __FCEU_QSCRIPT_ENABLE__
QtScriptManager::getInstance()->frameFinishedUpdate();
#endif
transferVideoBuffer();
}
@ -4569,15 +4609,18 @@ void consoleWin_t::updatePeriodic(void)
FCEU_PROFILE_FUNC(prof, "updatePeriodic");
static bool eventProcessingInProg = false;
if ( eventProcessingInProg )
{ // Prevent recursion as processEvents function can double back on us
return;
//if ( eventProcessingInProg )
//{ // Prevent recursion as processEvents function can double back on us
// return;
//}
// Prevent recursion as processEvents function can double back on us
if ( !eventProcessingInProg )
{
eventProcessingInProg = true;
// Process all events before attempting to render viewport
QCoreApplication::processEvents();
eventProcessingInProg = false;
}
eventProcessingInProg = true;
// Process all events before attempting to render viewport
QCoreApplication::processEvents();
eventProcessingInProg = false;
// Update Input Devices
FCEUD_UpdateInput();
@ -4863,6 +4906,7 @@ void emulatorThread_t::run(void)
void emulatorThread_t::signalFrameFinished(void)
{
emuSignalSendMark();
emit frameFinished();
}

View File

@ -206,6 +206,7 @@ class consoleWin_t : public QMainWindow
QAction *quickLoadAct;
QAction *quickSaveAct;
QAction *loadLuaAct;
QAction *loadJsAct;
QAction *scrShotAct;
QAction *quitAct;
QAction *inputConfig;
@ -372,6 +373,7 @@ class consoleWin_t : public QMainWindow
void incrementState(void);
void decrementState(void);
void loadLua(void);
void loadJs(void);
void takeScreenShot(void);
void prepareScreenShot(void);
void powerConsoleCB(void);

View File

@ -89,6 +89,7 @@ FrameTimingDialog_t::FrameTimingDialog_t(QWidget *parent)
frameTimeIdlePct = new QTreeWidgetItem();
frameLateCount = new QTreeWidgetItem();
videoTimeAbs = new QTreeWidgetItem();
emuSignalDelay = new QTreeWidgetItem();
tree->addTopLevelItem(frameTimeAbs);
tree->addTopLevelItem(frameTimeDel);
@ -97,6 +98,7 @@ FrameTimingDialog_t::FrameTimingDialog_t(QWidget *parent)
tree->addTopLevelItem(frameTimeWorkPct);
tree->addTopLevelItem(frameTimeIdlePct);
tree->addTopLevelItem(videoTimeAbs);
tree->addTopLevelItem(emuSignalDelay);
tree->addTopLevelItem(frameLateCount);
frameTimeAbs->setFlags(Qt::ItemIsEnabled | Qt::ItemNeverHasChildren);
@ -109,6 +111,7 @@ FrameTimingDialog_t::FrameTimingDialog_t(QWidget *parent)
frameTimeWorkPct->setText(0, tr("Frame Work %"));
frameTimeIdlePct->setText(0, tr("Frame Idle %"));
frameLateCount->setText(0, tr("Frame Late Count"));
emuSignalDelay->setText(0, tr("EMU Signal Delay ms"));
videoTimeAbs->setText(0, tr("Video Period ms"));
frameTimeAbs->setTextAlignment(0, Qt::AlignLeft);
@ -119,6 +122,7 @@ FrameTimingDialog_t::FrameTimingDialog_t(QWidget *parent)
frameTimeIdlePct->setTextAlignment(0, Qt::AlignLeft);
frameLateCount->setTextAlignment(0, Qt::AlignLeft);
videoTimeAbs->setTextAlignment(0, Qt::AlignLeft);
emuSignalDelay->setTextAlignment(0, Qt::AlignLeft);
for (int i = 0; i < 4; i++)
{
@ -130,6 +134,7 @@ FrameTimingDialog_t::FrameTimingDialog_t(QWidget *parent)
frameTimeIdlePct->setTextAlignment(i + 1, Qt::AlignCenter);
frameLateCount->setTextAlignment(i + 1, Qt::AlignCenter);
videoTimeAbs->setTextAlignment(i + 1, Qt::AlignCenter);
emuSignalDelay->setTextAlignment(i + 1, Qt::AlignCenter);
}
hbox = new QHBoxLayout();
@ -294,6 +299,19 @@ void FrameTimingDialog_t::updateTimingStats(void)
sprintf(stmp, "%.3f", stats.videoTimeDel.max * 1e3);
videoTimeAbs->setText(4, tr(stmp));
// Emulator to GUI Thread Signal Delay
sprintf(stmp, "%.3f", stats.emuSignalDelay.tgt * 1e3);
emuSignalDelay->setText(1, tr(stmp));
sprintf(stmp, "%.3f", stats.emuSignalDelay.cur * 1e3);
emuSignalDelay->setText(2, tr(stmp));
sprintf(stmp, "%.3f", stats.emuSignalDelay.min * 1e3);
emuSignalDelay->setText(3, tr(stmp));
sprintf(stmp, "%.3f", stats.emuSignalDelay.max * 1e3);
emuSignalDelay->setText(4, tr(stmp));
// Late Count
sprintf(stmp, "%u", stats.lateCount);
frameLateCount->setText(1, tr("0"));

View File

@ -40,6 +40,7 @@ protected:
QTreeWidgetItem *frameTimeIdlePct;
QTreeWidgetItem *frameLateCount;
QTreeWidgetItem *videoTimeAbs;
QTreeWidgetItem *emuSignalDelay;
QGroupBox *statFrame;
QTreeWidget *tree;

View File

@ -0,0 +1,653 @@
/* FCE Ultra - NES/Famicom Emulator
*
* Copyright notice for this file:
* Copyright (C) 2020 thor
*
* 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
*/
// QtScriptManager.cpp
//
#ifdef __FCEU_QSCRIPT_ENABLE__
#include <stdio.h>
#include <string.h>
#include <list>
#ifdef WIN32
#include <Windows.h>
#endif
#include <QTextEdit>
#include <QFileDialog>
#include <QMessageBox>
#include <QSettings>
#include <QJSValueIterator>
#include "../../fceu.h"
#include "../../movie.h"
#include "common/os_utils.h"
#include "Qt/QtScriptManager.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"
#include "Qt/ConsoleWindow.h"
//----------------------------------------------------
//---- EMU Script Object
//----------------------------------------------------
EmuScriptObject::EmuScriptObject(QObject* parent)
: QObject(parent)
{
}
//----------------------------------------------------
EmuScriptObject::~EmuScriptObject()
{
}
//----------------------------------------------------
void EmuScriptObject::print(const QString& msg)
{
if (dialog != nullptr)
{
dialog->logOutput(msg);
}
}
//----------------------------------------------------
void EmuScriptObject::softreset()
{
fceuWrapperSoftReset();
}
//----------------------------------------------------
void EmuScriptObject::pause()
{
FCEUI_SetEmulationPaused( EMULATIONPAUSED_PAUSED );
}
//----------------------------------------------------
void EmuScriptObject::unpause()
{
FCEUI_SetEmulationPaused(0);
}
//----------------------------------------------------
//---- Qt Script Instance
//----------------------------------------------------
QtScriptInstance::QtScriptInstance(QObject* parent)
: QObject(parent)
{
QScriptDialog_t* win = qobject_cast<QScriptDialog_t*>(parent);
emu = new EmuScriptObject(this);
if (win != nullptr)
{
dialog = win;
emu->setDialog(dialog);
}
engine = new QJSEngine(this);
configEngine();
QtScriptManager::getInstance()->addScriptInstance(this);
}
//----------------------------------------------------
QtScriptInstance::~QtScriptInstance()
{
if (engine != nullptr)
{
engine->deleteLater();
engine = nullptr;
}
QtScriptManager::getInstance()->removeScriptInstance(this);
//printf("QtScriptInstance Destroyed\n");
}
//----------------------------------------------------
void QtScriptInstance::resetEngine()
{
if (engine != nullptr)
{
engine->deleteLater();
engine = nullptr;
}
engine = new QJSEngine(this);
configEngine();
}
//----------------------------------------------------
int QtScriptInstance::configEngine()
{
engine->installExtensions(QJSEngine::ConsoleExtension);
QJSValue emuObject = engine->newQObject(emu);
engine->globalObject().setProperty("emu", emuObject);
onFrameFinishCallback = QJSValue();
return 0;
}
//----------------------------------------------------
int QtScriptInstance::loadScriptFile( QString filepath )
{
QFile scriptFile(filepath);
if (!scriptFile.open(QIODevice::ReadOnly))
{
return -1;
}
QTextStream stream(&scriptFile);
QString fileText = stream.readAll();
scriptFile.close();
FCEU_WRAPPER_LOCK();
QJSValue evalResult = engine->evaluate(fileText, filepath);
FCEU_WRAPPER_UNLOCK();
if (evalResult.isError())
{
print(evalResult.toString());
return -1;
}
else
{
//printf("Script Evaluation Success!\n");
}
onFrameFinishCallback = engine->globalObject().property("onFrameFinish");
return 0;
}
//----------------------------------------------------
void QtScriptInstance::print(const QString& msg)
{
if (dialog)
{
dialog->logOutput(msg);
}
}
//----------------------------------------------------
void QtScriptInstance::printSymbols(QJSValue& val, int iter)
{
int i=0;
if (iter > 10)
{
return;
}
QJSValueIterator it(val);
while (it.hasNext())
{
it.next();
QJSValue child = it.value();
qDebug() << iter << ":" << i << " " << it.name() << ": " << child.toString();
bool isPrototype = it.name() == "prototype";
if (!isPrototype)
{
printSymbols(child, iter + 1);
}
i++;
}
}
//----------------------------------------------------
int QtScriptInstance::call(const QString& funcName, const QJSValueList& args)
{
if (engine == nullptr)
{
return -1;
}
if (!engine->globalObject().hasProperty(funcName))
{
print(QString("No function exists: ") + funcName);
return -1;
}
QJSValue func = engine->globalObject().property(funcName);
FCEU_WRAPPER_LOCK();
QJSValue callResult = func.call(args);
FCEU_WRAPPER_UNLOCK();
if (callResult.isError())
{
print(callResult.toString());
}
else
{
//printf("Script Call Success!\n");
}
QJSValue global = engine->globalObject();
printSymbols( global );
return 0;
}
//----------------------------------------------------
void QtScriptInstance::onFrameFinish()
{
if (onFrameFinishCallback.isCallable())
{
onFrameFinishCallback.call();
}
}
//----------------------------------------------------
bool QtScriptInstance::isRunning()
{
return false;
}
//----------------------------------------------------
//---- Qt Script Manager
//----------------------------------------------------
QtScriptManager* QtScriptManager::_instance = nullptr;
QtScriptManager::QtScriptManager(QObject* parent)
: QObject(parent)
{
_instance = this;
}
//----------------------------------------------------
QtScriptManager::~QtScriptManager()
{
_instance = nullptr;
//printf("QtScriptManager destroyed\n");
}
//----------------------------------------------------
QtScriptManager* QtScriptManager::create(QObject* parent)
{
QtScriptManager* mgr = new QtScriptManager(parent);
//printf("QtScriptManager created\n");
return mgr;
}
//----------------------------------------------------
void QtScriptManager::addScriptInstance(QtScriptInstance* script)
{
scriptList.push_back(script);
}
//----------------------------------------------------
void QtScriptManager::removeScriptInstance(QtScriptInstance* script)
{
auto it = scriptList.begin();
while (it != scriptList.end())
{
if (*it == script)
{
it = scriptList.erase(it);
}
else
{
it++;
}
}
}
//----------------------------------------------------
void QtScriptManager::frameFinishedUpdate()
{
FCEU_WRAPPER_LOCK();
for (auto script : scriptList)
{
script->onFrameFinish();
}
FCEU_WRAPPER_UNLOCK();
}
//----------------------------------------------------
//---- Qt Script Dialog Window
//----------------------------------------------------
QScriptDialog_t::QScriptDialog_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("Qt Java Script Control"));
mainLayout = new QVBoxLayout();
lbl = new QLabel(tr("Script File:"));
scriptPath = new QLineEdit();
scriptArgs = new QLineEdit();
g_config->getOption("SDL.LastLoadJs", &filename);
scriptPath->setText( tr(filename.c_str()) );
scriptPath->setClearButtonEnabled(true);
scriptArgs->setClearButtonEnabled(true);
jsOutput = new QTextEdit();
jsOutput->setReadOnly(true);
hbox = new QHBoxLayout();
browseButton = new QPushButton(tr("Browse"));
stopButton = new QPushButton(tr("Stop"));
scriptInstance = new QtScriptInstance(this);
if (scriptInstance->isRunning())
{
startButton = new QPushButton(tr("Restart"));
}
else
{
startButton = new QPushButton(tr("Start"));
}
stopButton->setEnabled(scriptInstance->isRunning());
connect(browseButton, SIGNAL(clicked()), this, SLOT(openScriptFile(void)));
connect(stopButton, SIGNAL(clicked()), this, SLOT(stopScript(void)));
connect(startButton, SIGNAL(clicked()), this, SLOT(startScript(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(jsOutput);
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, &QScriptDialog_t::updatePeriodic);
periodicTimer->start(200); // 5hz
restoreGeometry(settings.value("QScriptWindow/geometry").toByteArray());
}
//----------------------------------------------------
QScriptDialog_t::~QScriptDialog_t(void)
{
QSettings settings;
std::list<QScriptDialog_t *>::iterator it;
//printf("Destroy JS Control Window\n");
periodicTimer->stop();
//for (it = winList.begin(); it != winList.end(); it++)
//{
// if ((*it) == this)
// {
// winList.erase(it);
// //printf("Removing JS Window\n");
// break;
// }
//}
settings.setValue("QScriptWindow/geometry", saveGeometry());
}
//----------------------------------------------------
void QScriptDialog_t::closeEvent(QCloseEvent *event)
{
//printf("JS Control Close Window Event\n");
done(0);
deleteLater();
event->accept();
}
//----------------------------------------------------
void QScriptDialog_t::closeWindow(void)
{
//printf("JS Control Close Window\n");
done(0);
deleteLater();
}
//----------------------------------------------------
void QScriptDialog_t::updatePeriodic(void)
{
// TODO
//printf("Update JS\n");
//if (updateJSDisplay)
//{
// updateJSWindows();
// updateJSDisplay = false;
//}
}
//----------------------------------------------------
void QScriptDialog_t::openJSKillMessageBox(void)
{
int ret;
QMessageBox msgBox(this);
msgBox.setIcon(QMessageBox::Warning);
msgBox.setText(tr("The JS 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)
{
}
}
//----------------------------------------------------
void QScriptDialog_t::openScriptFile(void)
{
int ret, useNativeFileDialogVal;
QString filename;
std::string last;
std::string dir;
const char *exePath = nullptr;
const char *jsPath = nullptr;
QFileDialog dialog(this, tr("Open JS Script"));
QList<QUrl> urls;
QDir d;
exePath = fceuExecutablePath();
//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) + "/../jsScripts");
if (d.exists())
{
urls << QUrl::fromLocalFile(d.absolutePath());
}
}
#ifndef WIN32
d.setPath("/usr/share/fceux/jsScripts");
if (d.exists())
{
urls << QUrl::fromLocalFile(d.absolutePath());
}
#endif
jsPath = getenv("FCEU_QSCRIPT_PATH");
// Parse LUA_PATH and add to urls
if (jsPath)
{
int i, j;
char stmp[2048];
i = j = 0;
while (jsPath[i] != 0)
{
if (jsPath[i] == ';')
{
stmp[j] = 0;
if (j > 0)
{
d.setPath(stmp);
if (d.exists())
{
urls << QUrl::fromLocalFile(d.absolutePath());
}
}
j = 0;
}
else
{
stmp[j] = jsPath[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("JS Scripts (*.js *.JS) ;; All files (*)"));
dialog.setViewMode(QFileDialog::List);
dialog.setFilter(QDir::AllEntries | QDir::AllDirs | QDir::Hidden);
dialog.setLabelText(QFileDialog::Accept, tr("Load"));
g_config->getOption("SDL.LastLoadJs", &last);
if (last.size() == 0)
{
#ifdef WIN32
last.assign(FCEUI_GetBaseDirectory());
#else
last.assign("/usr/share/fceux/jsScripts");
#endif
}
getDirFromFile(last.c_str(), dir);
dialog.setDirectory(tr(dir.c_str()));
// 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.LastLoadJs", filename.toStdString().c_str());
scriptPath->setText(filename);
}
//----------------------------------------------------
void QScriptDialog_t::startScript(void)
{
scriptInstance->resetEngine();
if (scriptInstance->loadScriptFile(scriptPath->text()))
{
// Script parsing error
return;
}
// TODO add option to pass options to script main.
QJSValue argArray = scriptInstance->getEngine()->newArray(4);
argArray.setProperty(0, "arg1");
argArray.setProperty(1, "arg2");
argArray.setProperty(2, "arg3");
QJSValueList argList = { argArray };
scriptInstance->call("main", argList);
}
//----------------------------------------------------
void QScriptDialog_t::stopScript(void)
{
}
//----------------------------------------------------
void QScriptDialog_t::refreshState(void)
{
if (scriptInstance->isRunning())
{
stopButton->setEnabled(true);
startButton->setText(tr("Restart"));
}
else
{
stopButton->setEnabled(false);
startButton->setText(tr("Start"));
}
}
//----------------------------------------------------
void QScriptDialog_t::logOutput(const QString& text)
{
jsOutput->insertPlainText(text);
}
//----------------------------------------------------
#endif // __FCEU_QSCRIPT_ENABLE__

View File

@ -0,0 +1,144 @@
// QtScriptManager.h
//
#pragma once
#ifdef __FCEU_QSCRIPT_ENABLE__
#include <stdio.h>
#include <stdarg.h>
#include <QWidget>
#include <QDialog>
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QComboBox>
#include <QCheckBox>
#include <QPushButton>
#include <QLabel>
#include <QFrame>
#include <QGroupBox>
#include <QLineEdit>
#include <QTextEdit>
#include <QJSEngine>
#include "Qt/main.h"
#include "utils/timeStamp.h"
class QScriptDialog_t;
class EmuScriptObject: public QObject
{
Q_OBJECT
public:
EmuScriptObject(QObject* parent = nullptr);
~EmuScriptObject();
void setDialog(QScriptDialog_t* _dialog){ dialog = _dialog; }
private:
QScriptDialog_t* dialog = nullptr;
public slots:
Q_INVOKABLE void print(const QString& msg);
Q_INVOKABLE void softreset();
Q_INVOKABLE void pause();
Q_INVOKABLE void unpause();
};
class QtScriptInstance : public QObject
{
Q_OBJECT
public:
QtScriptInstance(QObject* parent = nullptr);
~QtScriptInstance();
void resetEngine();
int loadScriptFile(QString filepath);
bool isRunning();
int call(const QString& funcName, const QJSValueList& args = QJSValueList());
void onFrameFinish();
QJSEngine* getEngine(){ return engine; };
private:
int configEngine();
void printSymbols(QJSValue& val, int iter = 0);
QJSEngine* engine = nullptr;
QScriptDialog_t* dialog = nullptr;
EmuScriptObject* emu = nullptr;
QJSValue onFrameFinishCallback;
public slots:
Q_INVOKABLE void print(const QString& msg);
};
class QtScriptManager : public QObject
{
Q_OBJECT
public:
QtScriptManager(QObject* parent = nullptr);
~QtScriptManager();
static QtScriptManager* getInstance(){ return _instance; }
static QtScriptManager* create(QObject* parent = nullptr);
void addScriptInstance(QtScriptInstance* script);
void removeScriptInstance(QtScriptInstance* script);
private:
static QtScriptManager* _instance;
QList<QtScriptInstance*> scriptList;
FCEU::timeStampRecord lastFrameUpdate;
public slots:
void frameFinishedUpdate();
};
class QScriptDialog_t : public QDialog
{
Q_OBJECT
public:
QScriptDialog_t(QWidget *parent = nullptr);
~QScriptDialog_t(void);
void refreshState(void);
void logOutput(const QString& text);
protected:
void closeEvent(QCloseEvent *bar);
void openJSKillMessageBox(void);
QTimer *periodicTimer;
QLineEdit *scriptPath;
QLineEdit *scriptArgs;
QPushButton *browseButton;
QPushButton *stopButton;
QPushButton *startButton;
QTextEdit *jsOutput;
QtScriptInstance *scriptInstance;
private:
public slots:
void closeWindow(void);
private slots:
void updatePeriodic(void);
void openScriptFile(void);
void startScript(void);
void stopScript(void);
};
// Formatted print
//int LuaPrintfToWindowConsole( __FCEU_PRINTF_FORMAT const char *format, ...) __FCEU_PRINTF_ATTRIBUTE( 1, 2 );
//void PrintToWindowConsole(intptr_t hDlgAsInt, const char *str);
//int LuaKillMessageBox(void);
#endif // __FCEU_QSCRIPT_ENABLE__

View File

@ -801,6 +801,7 @@ InitConfig()
config->addOption("_lastsavestateas", "SDL.LastSaveStateAs", savPath );
config->addOption("_lastopenmovie", "SDL.LastOpenMovie", movPath);
config->addOption("_lastloadlua", "SDL.LastLoadLua", "");
config->addOption("_lastloadjs", "SDL.LastLoadJs", "");
config->addOption("SDL.HelpFilePath", "");
config->addOption("SDL.AviFilePath", "");
config->addOption("SDL.WavFilePath", "");

View File

@ -39,6 +39,7 @@ static const double Normal = 1.0; // 1x speed (around 60 fps on NTSC)
static uint32 frameLateCounter = 0;
static FCEU::timeStampRecord Lasttime, Nexttime, Latetime;
static FCEU::timeStampRecord DesiredFrameTime, HalfFrameTime, QuarterFrameTime, DoubleFrameTime;
static FCEU::timeStampRecord emuSignalTx, guiSignalRx, emuSignalLatency;
static double desired_frametime = (1.0 / 60.099823);
static double desired_frameRate = (60.099823);
static double baseframeRate = (60.099823);
@ -52,6 +53,9 @@ static double videoLastTs = 0.0;
static double videoPeriodCur = 0.0;
static double videoPeriodMin = 1.0;
static double videoPeriodMax = 0.0;
static double emuLatencyCur = 0.0;
static double emuLatencyMin = 1.0;
static double emuLatencyMax = 0.0;
static bool keepFrameTimeStats = false;
static int InFrame = 0;
double g_fpsScale = Normal; // used by sdl.cpp
@ -193,6 +197,11 @@ int getFrameTimingStats( struct frameTimingStat_t *stats )
stats->videoTimeDel.min = videoPeriodMin;
stats->videoTimeDel.max = videoPeriodMax;
stats->emuSignalDelay.tgt = 0.0;
stats->emuSignalDelay.cur = emuLatencyCur;
stats->emuSignalDelay.min = emuLatencyMin;
stats->emuSignalDelay.max = emuLatencyMax;
return 0;
}
@ -216,6 +225,34 @@ void videoBufferSwapMark(void)
}
}
void emuSignalSendMark(void)
{
if ( keepFrameTimeStats )
{
emuSignalTx.readNew();
}
}
void guiSignalRecvMark(void)
{
if ( keepFrameTimeStats )
{
guiSignalRx.readNew();
emuSignalLatency = guiSignalRx - emuSignalTx;
emuLatencyCur = emuSignalLatency.toSeconds();
if ( emuLatencyCur < emuLatencyMin )
{
emuLatencyMin = emuLatencyCur;
}
if ( emuLatencyCur > emuLatencyMax )
{
emuLatencyMax = emuLatencyCur;
}
}
}
void resetFrameTiming(void)
{
frameLateCounter = 0;
@ -225,6 +262,8 @@ void resetFrameTiming(void)
frameIdleMin = 1.0;
videoPeriodMin = 1.0;
videoPeriodMax = 0.0;
emuLatencyMin = 1.0;
emuLatencyMax = 0.0;
}
/* LOGMUL = exp(log(2) / 3)

View File

@ -43,6 +43,13 @@ struct frameTimingStat_t
double max;
} videoTimeDel;
struct {
double tgt;
double cur;
double min;
double max;
} emuSignalDelay;
unsigned int lateCount;
bool enabled;
@ -52,6 +59,8 @@ void resetFrameTiming(void);
void setFrameTimingEnable( bool enable );
int getFrameTimingStats( struct frameTimingStat_t *stats );
void videoBufferSwapMark(void);
void emuSignalSendMark(void);
void guiSignalRecvMark(void);
double getHighPrecTimeStamp(void);
double getFrameRate(void);
double getFrameRateAdjustmentRatio(void);