Merge branch 'port/qt'

This commit is contained in:
Jeffrey Pfau 2014-10-27 22:03:02 -07:00
commit aed170b670
39 changed files with 3489 additions and 6 deletions

View File

@ -7,6 +7,7 @@ set(USE_GDB_STUB ON CACHE BOOL "Whether or not to enable the GDB stub ARM debugg
set(USE_FFMPEG ON CACHE BOOL "Whether or not to enable FFmpeg support")
set(USE_PNG ON CACHE BOOL "Whether or not to enable PNG support")
set(USE_LIBZIP ON CACHE BOOL "Whether or not to enable ZIP support")
set(BUILD_QT ON CACHE BOOL "Build Qt frontend")
set(BUILD_SDL ON CACHE BOOL "Build SDL frontend")
set(BUILD_PERF OFF CACHE BOOL "Build performance profiling tool")
file(GLOB ARM_SRC ${CMAKE_SOURCE_DIR}/src/arm/*.c)
@ -160,9 +161,14 @@ install(TARGETS ${BINARY_NAME} DESTINATION lib)
set_target_properties(${BINARY_NAME} PROPERTIES VERSION ${LIB_VERSION_STRING} SOVERSION ${LIB_VERSION_ABI})
if(BUILD_SDL)
add_definitions(-DBUILD_SDL)
add_subdirectory(${CMAKE_SOURCE_DIR}/src/platform/sdl ${CMAKE_BINARY_DIR}/sdl)
endif()
if(BUILD_QT)
add_subdirectory(${CMAKE_SOURCE_DIR}/src/platform/qt ${CMAKE_BINARY_DIR}/qt)
endif()
if(BUILD_PERF)
set(PERF_SRC ${CMAKE_SOURCE_DIR}/src/platform/perf-main.c)
if(UNIX AND NOT APPLE)
@ -183,5 +189,6 @@ message(STATUS " Video recording: ${USE_FFMPEG}")
message(STATUS " Screenshot/advanced savestate support: ${USE_PNG}")
message(STATUS " ZIP support: ${USE_LIBZIP}")
message(STATUS "Frontend summary:")
message(STATUS " Qt: ${BUILD_QT}")
message(STATUS " SDL (${SDL_VERSION}): ${BUILD_SDL}")
message(STATUS " Profiling: ${BUILD_PERF}")

45
res/info.plist.in Normal file
View File

@ -0,0 +1,45 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>English</string>
<key>CFBundleExecutable</key>
<string>${MACOSX_BUNDLE_EXECUTABLE_NAME}</string>
<key>CFBundleGetInfoString</key>
<string>${MACOSX_BUNDLE_INFO_STRING}</string>
<key>CFBundleIconFile</key>
<string>${MACOSX_BUNDLE_ICON_FILE}</string>
<key>CFBundleIdentifier</key>
<string>${MACOSX_BUNDLE_GUI_IDENTIFIER}</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>${MACOSX_BUNDLE_BUNDLE_NAME}</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>${MACOSX_BUNDLE_BUNDLE_VERSION}</string>
<key>CSResourcesFileMapped</key>
<true/>
<key>NSHighResolutionCapable</key>
<true/>
<key>NSHumanReadableCopyright</key>
<string>${MACOSX_BUNDLE_COPYRIGHT}</string>
<key>CFBundleDocumentTypes</key>
<array>
<dict>
<key>CFBundleTypeExtensions</key>
<array>
<string>gba</string>
</array>
<key>CFBundleTypeName</key>
<string>Game Boy Advance ROM Image</string>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
</dict>
</array>
</dict>
</plist>

BIN
res/mgba-1024.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 525 KiB

BIN
res/mgba.icns Normal file

Binary file not shown.

BIN
res/mgba.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 279 KiB

1
res/mgba.rc Normal file
View File

@ -0,0 +1 @@
IDI_ICON1 ICON DISCARDABLE "mgba.ico"

View File

@ -0,0 +1,46 @@
#include "AudioDevice.h"
extern "C" {
#include "gba.h"
#include "gba-audio.h"
#include "gba-thread.h"
}
using namespace QGBA;
AudioDevice::AudioDevice(QObject* parent)
: QIODevice(parent)
, m_context(nullptr)
, m_drift(0)
{
setOpenMode(ReadOnly);
}
void AudioDevice::setFormat(const QAudioFormat& format) {
if (!GBAThreadHasStarted(m_context)) {
return;
}
GBAThreadInterrupt(m_context);
m_ratio = GBAAudioCalculateRatio(&m_context->gba->audio, m_context->fpsTarget, format.sampleRate());
GBAThreadContinue(m_context);
}
void AudioDevice::setInput(GBAThread* input) {
m_context = input;
}
qint64 AudioDevice::readData(char* data, qint64 maxSize) {
if (maxSize > 0xFFFFFFFF) {
maxSize = 0xFFFFFFFF;
}
if (!m_context->gba) {
return 0;
}
return GBAAudioResampleNN(&m_context->gba->audio, m_ratio, &m_drift, reinterpret_cast<GBAStereoSample*>(data), maxSize / sizeof(GBAStereoSample)) * sizeof(GBAStereoSample);
}
qint64 AudioDevice::writeData(const char*, qint64) {
return 0;
}

View File

@ -0,0 +1,31 @@
#ifndef QGBA_AUDIO_DEVICE
#define QGBA_AUDIO_DEVICE
#include <QAudioFormat>
#include <QIODevice>
struct GBAThread;
namespace QGBA {
class AudioDevice : public QIODevice {
Q_OBJECT
public:
AudioDevice(QObject* parent = nullptr);
void setInput(GBAThread* input);
void setFormat(const QAudioFormat& format);
protected:
virtual qint64 readData(char* data, qint64 maxSize) override;
virtual qint64 writeData(const char* data, qint64 maxSize) override;
private:
GBAThread* m_context;
float m_drift;
float m_ratio;
};
}
#endif

View File

@ -0,0 +1,67 @@
#include "AudioProcessor.h"
#include "AudioDevice.h"
#include <QAudioOutput>
extern "C" {
#include "gba-thread.h"
}
using namespace QGBA;
AudioProcessor::AudioProcessor(QObject* parent)
: QObject(parent)
, m_audioOutput(nullptr)
, m_device(nullptr)
{
}
void AudioProcessor::setInput(GBAThread* input) {
m_context = input;
if (m_device) {
m_device->setInput(input);
if (m_audioOutput) {
m_device->setFormat(m_audioOutput->format());
}
}
}
void AudioProcessor::start() {
if (!m_device) {
m_device = new AudioDevice(this);
}
if (!m_audioOutput) {
QAudioFormat format;
format.setSampleRate(44100);
format.setChannelCount(2);
format.setSampleSize(16);
format.setCodec("audio/pcm");
format.setByteOrder(QAudioFormat::LittleEndian);
format.setSampleType(QAudioFormat::SignedInt);
m_audioOutput = new QAudioOutput(format, this);
}
m_device->setInput(m_context);
m_device->setFormat(m_audioOutput->format());
m_audioOutput->setBufferSize(m_context->audioBuffers * 4);
m_audioOutput->start(m_device);
}
void AudioProcessor::pause() {
if (m_audioOutput) {
m_audioOutput->stop();
}
}
void AudioProcessor::setBufferSamples(int samples) {
QAudioFormat format = m_audioOutput->format();
m_audioOutput->setBufferSize(samples * format.channelCount() * format.sampleSize() / 8);
}
void AudioProcessor::inputParametersChanged() {
m_device->setFormat(m_audioOutput->format());
}

View File

@ -0,0 +1,36 @@
#ifndef QGBA_AUDIO_PROCESSOR
#define QGBA_AUDIO_PROCESSOR
#include <QObject>
struct GBAThread;
class QAudioOutput;
namespace QGBA {
class AudioDevice;
class AudioProcessor : public QObject {
Q_OBJECT
public:
AudioProcessor(QObject* parent = nullptr);
void setInput(GBAThread* input);
public slots:
void start();
void pause();
void setBufferSamples(int samples);
void inputParametersChanged();
private:
GBAThread* m_context;
QAudioOutput* m_audioOutput;
AudioDevice* m_device;
};
}
#endif

View File

@ -0,0 +1,66 @@
cmake_minimum_required(VERSION 2.8.8)
enable_language(CXX)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} --std=c++11")
if(BUILD_SDL)
if(NOT SDL_FOUND AND NOT SDL2_FOUND)
find_package(SDL 1.2 REQUIRED)
endif()
if(SDL2_FOUND)
link_directories(${SDL2_LIBDIR})
endif()
set(PLATFORM_LIBRARY "${PLATFORM_LIBRARY};${SDL_LIBRARY};${SDLMAIN_LIBRARY}")
set(PLATFORM_SRC ${PLATFORM_SRC} ${CMAKE_SOURCE_DIR}/src/platform/sdl/sdl-events.c)
include_directories(${SDL_INCLUDE_DIR} ${CMAKE_SOURCE_DIR}/src/platform/sdl)
endif()
set(CMAKE_AUTOMOC ON)
set(CMAKE_INCLUDE_CURRENT_DIR ON)
find_package(Qt5Multimedia)
find_package(Qt5OpenGL)
find_package(Qt5Widgets)
find_package(OpenGL)
if(NOT Qt5Multimedia_FOUND OR NOT Qt5OpenGL_FOUND OR NOT Qt5Widgets_FOUND OR NOT OPENGL_FOUND)
set(BUILD_QT OFF PARENT_SCOPE)
return()
endif()
set(SOURCE_FILES
AudioDevice.cpp
AudioProcessor.cpp
Display.cpp
GBAApp.cpp
GameController.cpp
LoadSaveState.cpp
LogView.cpp
SavestateButton.cpp
Window.cpp
VFileDevice.cpp
VideoView.cpp)
qt5_wrap_ui(UI_FILES
LoadSaveState.ui
LogView.ui
VideoView.ui)
if(USE_GDB_STUB)
set(SOURCE_FILES ${PLATFORM_SRC} ${SOURCE_FILES} GDBController.cpp GDBWindow.cpp)
endif()
set(MACOSX_BUNDLE_ICON_FILE mgba.icns)
set(MACOSX_BUNDLE_BUNDLE_VERSION ${LIB_VERSION_STRING})
set(MACOSX_BUNDLE_BUNDLE_NAME ${PROJECT_NAME})
set(MACOSX_BUNDLE_GUI_IDENTIFIER com.endrift.${BINARY_NAME}-qt)
set_source_files_properties(${CMAKE_SOURCE_DIR}/res/mgba.icns PROPERTIES MACOSX_PACKAGE_LOCATION Resources)
qt5_add_resources(RESOURCES resources.qrc)
if(WIN32)
list(APPEND RESOURCES ${CMAKE_SOURCE_DIR}/res/mgba.rc)
endif()
add_executable(mGBA WIN32 MACOSX_BUNDLE main.cpp ${CMAKE_SOURCE_DIR}/res/mgba.icns ${SOURCE_FILES} ${UI_FILES} ${RESOURCES})
set_target_properties(mGBA PROPERTIES MACOSX_BUNDLE_INFO_PLIST ${CMAKE_SOURCE_DIR}/res/info.plist.in)
qt5_use_modules(mGBA Widgets Multimedia OpenGL)
target_link_libraries(mGBA ${PLATFORM_LIBRARY} ${OPENGL_LIBRARY} ${BINARY_NAME} Qt5::Widgets)

176
src/platform/qt/Display.cpp Normal file
View File

@ -0,0 +1,176 @@
#include "Display.h"
#include <QApplication>
#include <QResizeEvent>
extern "C" {
#include "gba-thread.h"
}
using namespace QGBA;
static const GLint _glVertices[] = {
0, 0,
256, 0,
256, 256,
0, 256
};
static const GLint _glTexCoords[] = {
0, 0,
1, 0,
1, 1,
0, 1
};
Display::Display(QGLFormat format, QWidget* parent)
: QGLWidget(format, parent)
, m_painter(nullptr)
, m_drawThread(nullptr)
{
setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding);
setMinimumSize(VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS);
setAutoBufferSwap(false);
setCursor(Qt::BlankCursor);
}
void Display::startDrawing(const uint32_t* buffer, GBAThread* thread) {
if (m_drawThread) {
return;
}
m_drawThread = new QThread(this);
m_painter = new Painter(this);
m_painter->setContext(thread);
m_painter->setBacking(buffer);
m_painter->moveToThread(m_drawThread);
m_context = thread;
doneCurrent();
context()->moveToThread(m_drawThread);
connect(m_drawThread, SIGNAL(started()), m_painter, SLOT(start()));
m_drawThread->start(QThread::TimeCriticalPriority);
}
void Display::stopDrawing() {
if (m_drawThread) {
QMetaObject::invokeMethod(m_painter, "stop", Qt::BlockingQueuedConnection);
m_drawThread->exit();
m_drawThread = nullptr;
}
}
void Display::forceDraw() {
if (m_drawThread) {
QMetaObject::invokeMethod(m_painter, "forceDraw", Qt::QueuedConnection);
}
}
#ifdef USE_PNG
void Display::screenshot() {
GBAThreadInterrupt(m_context);
GBAThreadTakeScreenshot(m_context);
GBAThreadContinue(m_context);
}
#endif
void Display::initializeGL() {
glClearColor(0, 0, 0, 0);
glClear(GL_COLOR_BUFFER_BIT);
swapBuffers();
}
void Display::resizeEvent(QResizeEvent* event) {
if (m_drawThread) {
GBAThreadInterrupt(m_context);
GBASyncSuspendDrawing(&m_context->sync);
QMetaObject::invokeMethod(m_painter, "resize", Qt::BlockingQueuedConnection, Q_ARG(QSize, event->size()));
GBASyncResumeDrawing(&m_context->sync);
GBAThreadContinue(m_context);
}
}
Painter::Painter(Display* parent)
: m_gl(parent)
{
m_size = parent->size();
}
void Painter::setContext(GBAThread* context) {
m_context = context;
}
void Painter::setBacking(const uint32_t* backing) {
m_backing = backing;
}
void Painter::resize(const QSize& size) {
m_size = size;
m_gl->makeCurrent();
glViewport(0, 0, m_size.width() * m_gl->devicePixelRatio(), m_size.height() * m_gl->devicePixelRatio());
glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
m_gl->swapBuffers();
m_gl->doneCurrent();
}
void Painter::start() {
m_gl->makeCurrent();
glEnable(GL_TEXTURE_2D);
glGenTextures(1, &m_tex);
glBindTexture(GL_TEXTURE_2D, m_tex);
glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
glEnableClientState(GL_VERTEX_ARRAY);
glVertexPointer(2, GL_INT, 0, _glVertices);
glTexCoordPointer(2, GL_INT, 0, _glTexCoords);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrtho(0, 240, 160, 0, 0, 1);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
m_gl->doneCurrent();
m_drawTimer = new QTimer;
m_drawTimer->moveToThread(QThread::currentThread());
m_drawTimer->setInterval(0);
connect(m_drawTimer, SIGNAL(timeout()), this, SLOT(draw()));
m_drawTimer->start();
}
void Painter::draw() {
m_gl->makeCurrent();
if (GBASyncWaitFrameStart(&m_context->sync, m_context->frameskip)) {
glViewport(0, 0, m_size.width() * m_gl->devicePixelRatio(), m_size.height() * m_gl->devicePixelRatio());
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 256, 256, 0, GL_RGBA, GL_UNSIGNED_BYTE, m_backing);
glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
if (m_context->sync.videoFrameWait) {
glFlush();
}
}
GBASyncWaitFrameEnd(&m_context->sync);
m_gl->swapBuffers();
m_gl->doneCurrent();
}
void Painter::forceDraw() {
m_gl->makeCurrent();
glViewport(0, 0, m_size.width() * m_gl->devicePixelRatio(), m_size.height() * m_gl->devicePixelRatio());
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 256, 256, 0, GL_RGBA, GL_UNSIGNED_BYTE, m_backing);
glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
if (m_context->sync.videoFrameWait) {
glFlush();
}
m_gl->swapBuffers();
m_gl->doneCurrent();
}
void Painter::stop() {
m_drawTimer->stop();
delete m_drawTimer;
m_gl->makeCurrent();
glDeleteTextures(1, &m_tex);
glClear(GL_COLOR_BUFFER_BIT);
m_gl->swapBuffers();
m_gl->doneCurrent();
m_gl->context()->moveToThread(QApplication::instance()->thread());
}

65
src/platform/qt/Display.h Normal file
View File

@ -0,0 +1,65 @@
#ifndef QGBA_DISPLAY
#define QGBA_DISPLAY
#include <QGLWidget>
#include <QThread>
#include <QTimer>
struct GBAThread;
namespace QGBA {
class Painter;
class Display : public QGLWidget {
Q_OBJECT
public:
Display(QGLFormat format, QWidget* parent = nullptr);
public slots:
void startDrawing(const uint32_t* buffer, GBAThread* context);
void stopDrawing();
void forceDraw();
#ifdef USE_PNG
void screenshot();
#endif
protected:
virtual void initializeGL() override;
virtual void paintEvent(QPaintEvent*) override {};
virtual void resizeEvent(QResizeEvent*) override;
private:
Painter* m_painter;
QThread* m_drawThread;
GBAThread* m_context;
};
class Painter : public QObject {
Q_OBJECT
public:
Painter(Display* parent);
void setContext(GBAThread*);
void setBacking(const uint32_t*);
public slots:
void forceDraw();
void draw();
void start();
void stop();
void resize(const QSize& size);
private:
QTimer* m_drawTimer;
GBAThread* m_context;
const uint32_t* m_backing;
GLuint m_tex;
QGLWidget* m_gl;
QSize m_size;
};
}
#endif

View File

@ -0,0 +1,32 @@
#include "GBAApp.h"
#include "GameController.h"
#include <QFileOpenEvent>
using namespace QGBA;
GBAApp::GBAApp(int& argc, char* argv[])
: QApplication(argc, argv)
{
QApplication::setApplicationName(PROJECT_NAME);
QApplication::setApplicationVersion(PROJECT_VERSION);
if (parseCommandArgs(&m_opts, argc, argv, 0)) {
m_window.optionsPassed(&m_opts);
}
m_window.show();
}
GBAApp::~GBAApp() {
freeOptions(&m_opts);
}
bool GBAApp::event(QEvent* event) {
if (event->type() == QEvent::FileOpen) {
m_window.controller()->loadGame(static_cast<QFileOpenEvent*>(event)->file());
return true;
}
return QApplication::event(event);
}

34
src/platform/qt/GBAApp.h Normal file
View File

@ -0,0 +1,34 @@
#ifndef QGBA_APP_H
#define QGBA_APP_H
#include <QApplication>
#include "Window.h"
extern "C" {
#include "platform/commandline.h"
}
namespace QGBA {
class GameController;
class GBAApp : public QApplication {
Q_OBJECT
public:
GBAApp(int& argc, char* argv[]);
virtual ~GBAApp();
protected:
bool event(QEvent*);
private:
Window m_window;
StartupOptions m_opts;
};
}
#endif

View File

@ -0,0 +1,71 @@
#include "GDBController.h"
#include "GameController.h"
using namespace QGBA;
GDBController::GDBController(GameController* controller, QObject* parent)
: QObject(parent)
, m_gameController(controller)
, m_port(2345)
, m_bindAddress(0)
{
GDBStubCreate(&m_gdbStub);
}
ushort GDBController::port() {
return m_port;
}
uint32_t GDBController::bindAddress() {
return m_bindAddress;
}
bool GDBController::isAttached() {
return m_gameController->debugger() == &m_gdbStub.d;
}
void GDBController::setPort(ushort port) {
m_port = port;
}
void GDBController::setBindAddress(uint32_t bindAddress) {
m_bindAddress = bindAddress;
}
void GDBController::attach() {
if (isAttached()) {
return;
}
m_gameController->setDebugger(&m_gdbStub.d);
}
void GDBController::detach() {
if (!isAttached()) {
return;
}
bool wasPaused = m_gameController->isPaused();
disconnect(m_gameController, SIGNAL(frameAvailable(const uint32_t*)), this, SLOT(updateGDB()));
m_gameController->setPaused(true);
GDBStubShutdown(&m_gdbStub);
m_gameController->setDebugger(nullptr);
m_gameController->setPaused(wasPaused);
}
void GDBController::listen() {
if (!isAttached()) {
attach();
}
bool wasPaused = m_gameController->isPaused();
connect(m_gameController, SIGNAL(frameAvailable(const uint32_t*)), this, SLOT(updateGDB()));
m_gameController->setPaused(true);
GDBStubListen(&m_gdbStub, m_port, m_bindAddress);
m_gameController->setPaused(wasPaused);
}
void GDBController::updateGDB() {
bool wasPaused = m_gameController->isPaused();
m_gameController->setPaused(true);
GDBStubUpdate(&m_gdbStub);
m_gameController->setPaused(wasPaused);
}

View File

@ -0,0 +1,44 @@
#ifndef QGBA_GDB_CONTROLLER
#define QGBA_GDB_CONTROLLER
#include <QObject>
extern "C" {
#include "debugger/gdb-stub.h"
}
namespace QGBA {
class GameController;
class GDBController : public QObject {
Q_OBJECT
public:
GDBController(GameController* controller, QObject* parent = nullptr);
public:
ushort port();
uint32_t bindAddress();
bool isAttached();
public slots:
void setPort(ushort port);
void setBindAddress(uint32_t bindAddress);
void attach();
void detach();
void listen();
private slots:
void updateGDB();
private:
GDBStub m_gdbStub;
GameController* m_gameController;
ushort m_port;
uint32_t m_bindAddress;
};
}
#endif

View File

@ -0,0 +1,99 @@
#include "GDBWindow.h"
#include <QGridLayout>
#include <QGroupBox>
#include <QLabel>
#include <QLineEdit>
#include <QPushButton>
#include <QVBoxLayout>
#include "GDBController.h"
using namespace QGBA;
GDBWindow::GDBWindow(GDBController* controller, QWidget* parent)
: QWidget(parent)
, m_gdbController(controller)
{
setWindowFlags(windowFlags() & ~Qt::WindowFullscreenButtonHint);
QVBoxLayout* mainSegment = new QVBoxLayout;
setLayout(mainSegment);
QGroupBox* settings = new QGroupBox(tr("Server settings"));
mainSegment->addWidget(settings);
QGridLayout* settingsGrid = new QGridLayout;
settings->setLayout(settingsGrid);
QLabel* portLabel = new QLabel(tr("Local port"));
settingsGrid->addWidget(portLabel, 0, 0, Qt::AlignRight);
QLabel* bindAddressLabel = new QLabel(tr("Bind address"));
settingsGrid->addWidget(bindAddressLabel, 1, 0, Qt::AlignRight);
m_portEdit = new QLineEdit("2345");
m_portEdit->setMaxLength(5);
connect(m_portEdit, SIGNAL(textChanged(const QString&)), this, SLOT(portChanged(const QString&)));
settingsGrid->addWidget(m_portEdit, 0, 1, Qt::AlignLeft);
m_bindAddressEdit = new QLineEdit("0.0.0.0");
m_bindAddressEdit->setMaxLength(15);
connect(m_bindAddressEdit, SIGNAL(textChanged(const QString&)), this, SLOT(bindAddressChanged(const QString&)));
settingsGrid->addWidget(m_bindAddressEdit, 1, 1, Qt::AlignLeft);
m_startStopButton = new QPushButton;
mainSegment->addWidget(m_startStopButton);
if (m_gdbController->isAttached()) {
started();
} else {
stopped();
}
}
void GDBWindow::portChanged(const QString& portString) {
bool ok = false;
ushort port = portString.toUShort(&ok);
if (ok) {
m_gdbController->setPort(port);
}
}
void GDBWindow::bindAddressChanged(const QString& bindAddressString) {
bool ok = false;
QStringList parts = bindAddressString.split('.');
if (parts.length() != 4) {
return;
}
int i;
uint32_t address = 0;
for (i = 0; i < 4; ++i) {
ushort octet = parts[i].toUShort(&ok);
if (!ok) {
return;
}
if (octet > 0xFF) {
return;
}
address <<= 8;
address += octet;
}
m_gdbController->setBindAddress(address);
}
void GDBWindow::started() {
m_portEdit->setEnabled(false);
m_bindAddressEdit->setEnabled(false);
m_startStopButton->setText(tr("Stop"));
disconnect(m_startStopButton, SIGNAL(clicked()), m_gdbController, SLOT(listen()));
disconnect(m_startStopButton, SIGNAL(clicked()), this, SLOT(started()));
connect(m_startStopButton, SIGNAL(clicked()), m_gdbController, SLOT(detach()));
connect(m_startStopButton, SIGNAL(clicked()), this, SLOT(stopped()));
}
void GDBWindow::stopped() {
m_portEdit->setEnabled(true);
m_bindAddressEdit->setEnabled(true);
m_startStopButton->setText(tr("Start"));
disconnect(m_startStopButton, SIGNAL(clicked()), m_gdbController, SLOT(detach()));
disconnect(m_startStopButton, SIGNAL(clicked()), this, SLOT(stopped()));
connect(m_startStopButton, SIGNAL(clicked()), m_gdbController, SLOT(listen()));
connect(m_startStopButton, SIGNAL(clicked()), this, SLOT(started()));
}

View File

@ -0,0 +1,36 @@
#ifndef QGBA_GDB_WINDOW
#define QGBA_GDB_WINDOW
#include <QWidget>
class QLineEdit;
class QPushButton;
namespace QGBA {
class GDBController;
class GDBWindow : public QWidget {
Q_OBJECT
public:
GDBWindow(GDBController* controller, QWidget* parent = nullptr);
private slots:
void portChanged(const QString&);
void bindAddressChanged(const QString&);
void started();
void stopped();
private:
GDBController* m_gdbController;
QLineEdit* m_portEdit;
QLineEdit* m_bindAddressEdit;
QPushButton* m_startStopButton;
};
}
#endif

View File

@ -0,0 +1,376 @@
#include "GameController.h"
#include "AudioProcessor.h"
#include <QThread>
extern "C" {
#include "gba.h"
#include "gba-audio.h"
#include "gba-serialize.h"
#include "renderers/video-software.h"
#include "util/vfs.h"
}
using namespace QGBA;
GameController::GameController(QObject* parent)
: QObject(parent)
, m_drawContext(new uint32_t[256 * 256])
, m_threadContext()
, m_activeKeys(0)
, m_gameOpen(false)
, m_audioThread(new QThread(this))
, m_audioProcessor(new AudioProcessor)
, m_videoSync(VIDEO_SYNC)
, m_audioSync(AUDIO_SYNC)
, m_turbo(false)
, m_turboForced(false)
{
m_renderer = new GBAVideoSoftwareRenderer;
GBAVideoSoftwareRendererCreate(m_renderer);
m_renderer->outputBuffer = (color_t*) m_drawContext;
m_renderer->outputBufferStride = 256;
m_threadContext.state = THREAD_INITIALIZED;
m_threadContext.debugger = 0;
m_threadContext.frameskip = 0;
m_threadContext.bios = 0;
m_threadContext.renderer = &m_renderer->d;
m_threadContext.userData = this;
m_threadContext.rewindBufferCapacity = 0;
m_threadContext.logLevel = -1;
GBAInputMapInit(&m_threadContext.inputMap);
#ifdef BUILD_SDL
SDL_Init(SDL_INIT_JOYSTICK | SDL_INIT_NOPARACHUTE);
m_sdlEvents.bindings = &m_threadContext.inputMap;
GBASDLInitEvents(&m_sdlEvents);
SDL_JoystickEventState(SDL_QUERY);
#endif
m_threadContext.startCallback = [] (GBAThread* context) {
GameController* controller = static_cast<GameController*>(context->userData);
controller->m_audioProcessor->setInput(context);
controller->gameStarted(context);
};
m_threadContext.cleanCallback = [] (GBAThread* context) {
GameController* controller = static_cast<GameController*>(context->userData);
controller->gameStopped(context);
};
m_threadContext.frameCallback = [] (GBAThread* context) {
GameController* controller = static_cast<GameController*>(context->userData);
controller->m_pauseMutex.lock();
if (controller->m_pauseAfterFrame) {
GBAThreadPauseFromThread(context);
controller->m_pauseAfterFrame = false;
controller->gamePaused(&controller->m_threadContext);
}
controller->m_pauseMutex.unlock();
controller->frameAvailable(controller->m_drawContext);
};
m_threadContext.logHandler = [] (GBAThread* context, enum GBALogLevel level, const char* format, va_list args) {
GameController* controller = static_cast<GameController*>(context->userData);
controller->postLog(level, QString().vsprintf(format, args));
};
m_audioThread->start(QThread::TimeCriticalPriority);
m_audioProcessor->moveToThread(m_audioThread);
connect(this, SIGNAL(gameStarted(GBAThread*)), m_audioProcessor, SLOT(start()));
connect(this, SIGNAL(gameStopped(GBAThread*)), m_audioProcessor, SLOT(pause()));
connect(this, SIGNAL(gamePaused(GBAThread*)), m_audioProcessor, SLOT(pause()));
connect(this, SIGNAL(gameUnpaused(GBAThread*)), m_audioProcessor, SLOT(start()));
#ifdef BUILD_SDL
connect(this, SIGNAL(frameAvailable(const uint32_t*)), this, SLOT(testSDLEvents()));
#endif
}
GameController::~GameController() {
m_audioThread->quit();
m_audioThread->wait();
disconnect();
closeGame();
delete m_renderer;
}
ARMDebugger* GameController::debugger() {
return m_threadContext.debugger;
}
void GameController::setDebugger(ARMDebugger* debugger) {
bool wasPaused = isPaused();
setPaused(true);
if (m_threadContext.debugger && GBAThreadHasStarted(&m_threadContext)) {
GBADetachDebugger(m_threadContext.gba);
}
m_threadContext.debugger = debugger;
if (m_threadContext.debugger && GBAThreadHasStarted(&m_threadContext)) {
GBAAttachDebugger(m_threadContext.gba, m_threadContext.debugger);
}
setPaused(wasPaused);
}
void GameController::loadGame(const QString& path, bool dirmode) {
closeGame();
if (!dirmode) {
QFile file(path);
if (!file.open(QIODevice::ReadOnly)) {
return;
}
file.close();
}
m_fname = path;
m_dirmode = dirmode;
openGame();
}
void GameController::openGame() {
m_gameOpen = true;
m_pauseAfterFrame = false;
if (m_turbo) {
m_threadContext.sync.videoFrameWait = false;
m_threadContext.sync.audioWait = false;
} else {
m_threadContext.sync.videoFrameWait = m_videoSync;
m_threadContext.sync.audioWait = m_audioSync;
}
m_threadContext.fname = strdup(m_fname.toLocal8Bit().constData());
if (m_dirmode) {
m_threadContext.gameDir = VDirOpen(m_threadContext.fname);
m_threadContext.stateDir = m_threadContext.gameDir;
} else {
m_threadContext.rom = VFileOpen(m_threadContext.fname, O_RDONLY);
#if ENABLE_LIBZIP
m_threadContext.gameDir = VDirOpenZip(m_threadContext.fname, 0);
#endif
}
if (!m_bios.isNull()) {
m_threadContext.bios = VFileOpen(m_bios.toLocal8Bit().constData(), O_RDONLY);
}
if (!m_patch.isNull()) {
m_threadContext.patch = VFileOpen(m_patch.toLocal8Bit().constData(), O_RDONLY);
}
if (!GBAThreadStart(&m_threadContext)) {
m_gameOpen = false;
}
}
void GameController::loadBIOS(const QString& path) {
m_bios = path;
if (m_gameOpen) {
closeGame();
openGame();
}
}
void GameController::loadPatch(const QString& path) {
m_patch = path;
if (m_gameOpen) {
closeGame();
openGame();
}
}
void GameController::closeGame() {
if (!m_gameOpen) {
return;
}
if (GBAThreadIsPaused(&m_threadContext)) {
GBAThreadUnpause(&m_threadContext);
}
GBAThreadEnd(&m_threadContext);
GBAThreadJoin(&m_threadContext);
if (m_threadContext.fname) {
free(const_cast<char*>(m_threadContext.fname));
m_threadContext.fname = nullptr;
}
m_patch = QString();
m_gameOpen = false;
emit gameStopped(&m_threadContext);
}
bool GameController::isPaused() {
return GBAThreadIsPaused(&m_threadContext);
}
void GameController::setPaused(bool paused) {
if (paused == GBAThreadIsPaused(&m_threadContext)) {
return;
}
if (paused) {
GBAThreadPause(&m_threadContext);
emit gamePaused(&m_threadContext);
} else {
GBAThreadUnpause(&m_threadContext);
emit gameUnpaused(&m_threadContext);
}
}
void GameController::reset() {
GBAThreadReset(&m_threadContext);
}
void GameController::frameAdvance() {
m_pauseMutex.lock();
m_pauseAfterFrame = true;
setPaused(false);
m_pauseMutex.unlock();
}
void GameController::keyPressed(int key) {
int mappedKey = 1 << key;
m_activeKeys |= mappedKey;
updateKeys();
}
void GameController::keyReleased(int key) {
int mappedKey = 1 << key;
m_activeKeys &= ~mappedKey;
updateKeys();
}
void GameController::setAudioBufferSamples(int samples) {
GBAThreadInterrupt(&m_threadContext);
m_threadContext.audioBuffers = samples;
GBAAudioResizeBuffer(&m_threadContext.gba->audio, samples);
GBAThreadContinue(&m_threadContext);
QMetaObject::invokeMethod(m_audioProcessor, "setBufferSamples", Q_ARG(int, samples));
}
void GameController::setFPSTarget(float fps) {
GBAThreadInterrupt(&m_threadContext);
m_threadContext.fpsTarget = fps;
GBAThreadContinue(&m_threadContext);
QMetaObject::invokeMethod(m_audioProcessor, "inputParametersChanged");
}
void GameController::loadState(int slot) {
GBAThreadInterrupt(&m_threadContext);
GBALoadState(m_threadContext.gba, m_threadContext.stateDir, slot);
GBAThreadContinue(&m_threadContext);
emit stateLoaded(&m_threadContext);
emit frameAvailable(m_drawContext);
}
void GameController::saveState(int slot) {
GBAThreadInterrupt(&m_threadContext);
GBASaveState(m_threadContext.gba, m_threadContext.stateDir, slot, true);
GBAThreadContinue(&m_threadContext);
}
void GameController::setVideoSync(bool set) {
m_videoSync = set;
if (!m_turbo && m_gameOpen) {
GBAThreadInterrupt(&m_threadContext);
m_threadContext.sync.videoFrameWait = set;
GBAThreadContinue(&m_threadContext);
}
}
void GameController::setAudioSync(bool set) {
m_audioSync = set;
if (!m_turbo && m_gameOpen) {
GBAThreadInterrupt(&m_threadContext);
m_threadContext.sync.audioWait = set;
GBAThreadContinue(&m_threadContext);
}
}
void GameController::setFrameskip(int skip) {
m_threadContext.frameskip = skip;
}
void GameController::setTurbo(bool set, bool forced) {
if (m_turboForced && !forced) {
return;
}
m_turbo = set;
if (set) {
m_turboForced = forced;
} else {
m_turboForced = false;
}
if (m_gameOpen) {
GBAThreadInterrupt(&m_threadContext);
m_threadContext.sync.audioWait = set ? false : m_audioSync;
m_threadContext.sync.videoFrameWait = set ? false : m_videoSync;
GBAThreadContinue(&m_threadContext);
}
}
void GameController::setAVStream(GBAAVStream* stream) {
if (m_gameOpen) {
GBAThreadInterrupt(&m_threadContext);
m_threadContext.stream = stream;
GBAThreadContinue(&m_threadContext);
} else {
m_threadContext.stream = stream;
}
}
void GameController::clearAVStream() {
if (m_gameOpen) {
GBAThreadInterrupt(&m_threadContext);
m_threadContext.stream = nullptr;
GBAThreadContinue(&m_threadContext);
} else {
m_threadContext.stream = nullptr;
}
}
void GameController::updateKeys() {
int activeKeys = m_activeKeys;
#ifdef BUILD_SDL
activeKeys |= m_activeButtons;
#endif
m_threadContext.activeKeys = activeKeys;
}
#ifdef BUILD_SDL
void GameController::testSDLEvents() {
SDL_Joystick* joystick = m_sdlEvents.joystick;
SDL_JoystickUpdate();
int numButtons = SDL_JoystickNumButtons(joystick);
m_activeButtons = 0;
int i;
for (i = 0; i < numButtons; ++i) {
GBAKey key = GBAInputMapKey(&m_threadContext.inputMap, SDL_BINDING_BUTTON, i);
if (key == GBA_KEY_NONE) {
continue;
}
if (SDL_JoystickGetButton(joystick, i)) {
m_activeButtons |= 1 << key;
}
}
int numHats = SDL_JoystickNumHats(joystick);
for (i = 0; i < numHats; ++i) {
int hat = SDL_JoystickGetHat(joystick, i);
if (hat & SDL_HAT_UP) {
m_activeButtons |= 1 << GBA_KEY_UP;
}
if (hat & SDL_HAT_LEFT) {
m_activeButtons |= 1 << GBA_KEY_LEFT;
}
if (hat & SDL_HAT_DOWN) {
m_activeButtons |= 1 << GBA_KEY_DOWN;
}
if (hat & SDL_HAT_RIGHT) {
m_activeButtons |= 1 << GBA_KEY_RIGHT;
}
}
updateKeys();
}
#endif

View File

@ -0,0 +1,117 @@
#ifndef QGBA_GAME_CONTROLLER
#define QGBA_GAME_CONTROLLER
#include <QFile>
#include <QImage>
#include <QObject>
#include <QMutex>
#include <QString>
extern "C" {
#include "gba-thread.h"
#ifdef BUILD_SDL
#include "sdl-events.h"
#endif
}
struct GBAAudio;
struct GBAVideoSoftwareRenderer;
class QThread;
namespace QGBA {
class AudioProcessor;
class GameController : public QObject {
Q_OBJECT
public:
static const bool VIDEO_SYNC = false;
static const bool AUDIO_SYNC = true;
GameController(QObject* parent = nullptr);
~GameController();
const uint32_t* drawContext() const { return m_drawContext; }
GBAThread* thread() { return &m_threadContext; }
bool isPaused();
bool isLoaded() { return m_gameOpen; }
#ifdef USE_GDB_STUB
ARMDebugger* debugger();
void setDebugger(ARMDebugger*);
#endif
signals:
void frameAvailable(const uint32_t*);
void gameStarted(GBAThread*);
void gameStopped(GBAThread*);
void gamePaused(GBAThread*);
void gameUnpaused(GBAThread*);
void stateLoaded(GBAThread*);
void postLog(int level, const QString& log);
public slots:
void loadGame(const QString& path, bool dirmode = false);
void loadBIOS(const QString& path);
void loadPatch(const QString& path);
void openGame();
void closeGame();
void setPaused(bool paused);
void reset();
void frameAdvance();
void keyPressed(int key);
void keyReleased(int key);
void setAudioBufferSamples(int samples);
void setFPSTarget(float fps);
void loadState(int slot);
void saveState(int slot);
void setVideoSync(bool);
void setAudioSync(bool);
void setFrameskip(int);
void setTurbo(bool, bool forced = true);
void setAVStream(GBAAVStream*);
void clearAVStream();
#ifdef BUILD_SDL
private slots:
void testSDLEvents();
private:
GBASDLEvents m_sdlEvents;
int m_activeButtons;
#endif
private:
void updateKeys();
uint32_t* m_drawContext;
GBAThread m_threadContext;
GBAVideoSoftwareRenderer* m_renderer;
int m_activeKeys;
bool m_gameOpen;
bool m_dirmode;
QString m_fname;
QString m_bios;
QString m_patch;
QThread* m_audioThread;
AudioProcessor* m_audioProcessor;
QMutex m_pauseMutex;
bool m_pauseAfterFrame;
bool m_videoSync;
bool m_audioSync;
bool m_turbo;
bool m_turboForced;
};
}
#endif

View File

@ -0,0 +1,149 @@
#include "LoadSaveState.h"
#include "GameController.h"
#include "VFileDevice.h"
#include <QKeyEvent>
#include <QPainter>
extern "C" {
#include "gba-serialize.h"
#include "gba-video.h"
}
using namespace QGBA;
LoadSaveState::LoadSaveState(GameController* controller, QWidget* parent)
: QWidget(parent)
, m_controller(controller)
, m_currentFocus(0)
{
m_ui.setupUi(this);
m_slots[0] = m_ui.state1;
m_slots[1] = m_ui.state2;
m_slots[2] = m_ui.state3;
m_slots[3] = m_ui.state4;
m_slots[4] = m_ui.state5;
m_slots[5] = m_ui.state6;
m_slots[6] = m_ui.state7;
m_slots[7] = m_ui.state8;
m_slots[8] = m_ui.state9;
int i;
for (i = 0; i < NUM_SLOTS; ++i) {
loadState(i + 1);
m_slots[i]->installEventFilter(this);
connect(m_slots[i], &QAbstractButton::clicked, this, [this, i]() { triggerState(i + 1); });
}
}
void LoadSaveState::setMode(LoadSave mode) {
m_mode = mode;
QString text = mode == LoadSave::LOAD ? tr("Load State") : tr("Save State");
setWindowTitle(text);
m_ui.lsLabel->setText(text);
}
bool LoadSaveState::eventFilter(QObject* object, QEvent* event) {
if (event->type() == QEvent::KeyPress) {
int column = m_currentFocus % 3;
int row = m_currentFocus - column;
switch (static_cast<QKeyEvent*>(event)->key()) {
case Qt::Key_Up:
row += 6;
break;
case Qt::Key_Down:
row += 3;
break;
case Qt::Key_Left:
column += 2;
break;
case Qt::Key_Right:
column += 1;
break;
case Qt::Key_1:
case Qt::Key_2:
case Qt::Key_3:
case Qt::Key_4:
case Qt::Key_5:
case Qt::Key_6:
case Qt::Key_7:
case Qt::Key_8:
case Qt::Key_9:
triggerState(static_cast<QKeyEvent*>(event)->key() - Qt::Key_1 + 1);
break;
case Qt::Key_Escape:
close();
break;
case Qt::Key_Enter:
case Qt::Key_Return:
triggerState(m_currentFocus + 1);
break;
default:
return false;
}
column %= 3;
row %= 9;
m_currentFocus = column + row;
m_slots[m_currentFocus]->setFocus();
return true;
}
if (event->type() == QEvent::Enter) {
int i;
for (i = 0; i < 9; ++i) {
if (m_slots[i] == object) {
m_currentFocus = i;
m_slots[m_currentFocus]->setFocus();
return true;
}
}
}
return false;
}
void LoadSaveState::loadState(int slot) {
GBAThread* thread = m_controller->thread();
VFile* vf = GBAGetState(thread->gba, thread->stateDir, slot, false);
if (!vf) {
m_slots[slot - 1]->setText(tr("Empty"));
return;
}
VFileDevice vdev(vf);
QImage stateImage;
stateImage.load(&vdev, "PNG");
if (!stateImage.isNull()) {
QPixmap statePixmap;
statePixmap.convertFromImage(stateImage);
m_slots[slot - 1]->setIcon(statePixmap);
m_slots[slot - 1]->setText(QString());
} else {
m_slots[slot - 1]->setText(tr("Slot %1").arg(slot));
}
}
void LoadSaveState::triggerState(int slot) {
if (m_mode == LoadSave::SAVE) {
m_controller->saveState(slot);
} else {
m_controller->loadState(slot);
}
close();
}
void LoadSaveState::closeEvent(QCloseEvent* event) {
emit closed();
QWidget::closeEvent(event);
}
void LoadSaveState::showEvent(QShowEvent* event) {
m_slots[m_currentFocus]->setFocus();
QWidget::showEvent(event);
}
void LoadSaveState::paintEvent(QPaintEvent*) {
QPainter painter(this);
QRect full(QPoint(), size());
painter.drawPixmap(full, m_currentImage);
painter.fillRect(full, QColor(0, 0, 0, 128));
}

View File

@ -0,0 +1,52 @@
#ifndef QGBA_LOAD_SAVE_STATE
#define QGBA_LOAD_SAVE_STATE
#include <QWidget>
#include "ui_LoadSaveState.h"
namespace QGBA {
class GameController;
class SavestateButton;
enum class LoadSave {
LOAD,
SAVE
};
class LoadSaveState : public QWidget {
Q_OBJECT
public:
const static int NUM_SLOTS = 9;
LoadSaveState(GameController* controller, QWidget* parent = nullptr);
void setMode(LoadSave mode);
signals:
void closed();
protected:
virtual bool eventFilter(QObject*, QEvent*) override;
virtual void closeEvent(QCloseEvent*) override;
virtual void showEvent(QShowEvent*) override;
virtual void paintEvent(QPaintEvent*) override;
private:
void loadState(int slot);
void triggerState(int slot);
Ui::LoadSaveState m_ui;
GameController* m_controller;
SavestateButton* m_slots[NUM_SLOTS];
LoadSave m_mode;
int m_currentFocus;
QPixmap m_currentImage;
};
}
#endif

View File

@ -0,0 +1,328 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>LoadSaveState</class>
<widget class="QWidget" name="LoadSaveState">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>760</width>
<height>560</height>
</rect>
</property>
<property name="windowTitle">
<string>%1 State</string>
</property>
<layout class="QGridLayout" name="gridLayout_2" rowstretch="1,0,0,0" columnstretch="0,0,0">
<property name="leftMargin">
<number>6</number>
</property>
<property name="topMargin">
<number>6</number>
</property>
<property name="rightMargin">
<number>6</number>
</property>
<property name="bottomMargin">
<number>6</number>
</property>
<property name="spacing">
<number>2</number>
</property>
<item row="1" column="0">
<widget class="QGBA::SavestateButton" name="state1">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Maximum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="maximumSize">
<size>
<width>242</width>
<height>162</height>
</size>
</property>
<property name="text">
<string>No Save</string>
</property>
<property name="iconSize">
<size>
<width>240</width>
<height>160</height>
</size>
</property>
<property name="shortcut">
<string>1</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QGBA::SavestateButton" name="state2">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Maximum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="maximumSize">
<size>
<width>242</width>
<height>162</height>
</size>
</property>
<property name="text">
<string>No Save</string>
</property>
<property name="iconSize">
<size>
<width>240</width>
<height>160</height>
</size>
</property>
<property name="shortcut">
<string>2</string>
</property>
</widget>
</item>
<item row="0" column="0" colspan="3">
<widget class="QLabel" name="lsLabel">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Maximum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="styleSheet">
<string notr="true">font-size: 30pt; font-weight: bold; color: white;</string>
</property>
<property name="text">
<string>%1 State</string>
</property>
<property name="scaledContents">
<bool>false</bool>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item row="1" column="2">
<widget class="QGBA::SavestateButton" name="state3">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Maximum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="maximumSize">
<size>
<width>242</width>
<height>162</height>
</size>
</property>
<property name="text">
<string>No Save</string>
</property>
<property name="iconSize">
<size>
<width>240</width>
<height>160</height>
</size>
</property>
<property name="shortcut">
<string>3</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QGBA::SavestateButton" name="state4">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Maximum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="maximumSize">
<size>
<width>242</width>
<height>162</height>
</size>
</property>
<property name="text">
<string>No Save</string>
</property>
<property name="iconSize">
<size>
<width>240</width>
<height>160</height>
</size>
</property>
<property name="shortcut">
<string>4</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QGBA::SavestateButton" name="state5">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Maximum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="maximumSize">
<size>
<width>242</width>
<height>162</height>
</size>
</property>
<property name="text">
<string>No Save</string>
</property>
<property name="iconSize">
<size>
<width>240</width>
<height>160</height>
</size>
</property>
<property name="shortcut">
<string>5</string>
</property>
</widget>
</item>
<item row="2" column="2">
<widget class="QGBA::SavestateButton" name="state6">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Maximum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="maximumSize">
<size>
<width>242</width>
<height>162</height>
</size>
</property>
<property name="text">
<string>No Save</string>
</property>
<property name="iconSize">
<size>
<width>240</width>
<height>160</height>
</size>
</property>
<property name="shortcut">
<string>6</string>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QGBA::SavestateButton" name="state7">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Maximum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="maximumSize">
<size>
<width>242</width>
<height>162</height>
</size>
</property>
<property name="text">
<string>No Save</string>
</property>
<property name="iconSize">
<size>
<width>240</width>
<height>160</height>
</size>
</property>
<property name="shortcut">
<string>7</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QGBA::SavestateButton" name="state8">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Maximum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="maximumSize">
<size>
<width>242</width>
<height>162</height>
</size>
</property>
<property name="text">
<string>No Save</string>
</property>
<property name="iconSize">
<size>
<width>240</width>
<height>160</height>
</size>
</property>
<property name="shortcut">
<string>8</string>
</property>
</widget>
</item>
<item row="3" column="2">
<widget class="QGBA::SavestateButton" name="state9">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Maximum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="maximumSize">
<size>
<width>242</width>
<height>162</height>
</size>
</property>
<property name="text">
<string>No Save</string>
</property>
<property name="iconSize">
<size>
<width>240</width>
<height>160</height>
</size>
</property>
<property name="shortcut">
<string>9</string>
</property>
</widget>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>QGBA::SavestateButton</class>
<extends>QPushButton</extends>
<header>SavestateButton.h</header>
</customwidget>
</customwidgets>
<tabstops>
<tabstop>state1</tabstop>
<tabstop>state2</tabstop>
<tabstop>state3</tabstop>
<tabstop>state4</tabstop>
<tabstop>state5</tabstop>
<tabstop>state6</tabstop>
<tabstop>state7</tabstop>
<tabstop>state8</tabstop>
<tabstop>state9</tabstop>
</tabstops>
<resources/>
<connections/>
</ui>

144
src/platform/qt/LogView.cpp Normal file
View File

@ -0,0 +1,144 @@
#include "LogView.h"
#include <QTextBlock>
#include <QTextCursor>
using namespace QGBA;
LogView::LogView(QWidget* parent)
: QWidget(parent)
{
m_ui.setupUi(this);
connect(m_ui.levelDebug, SIGNAL(toggled(bool)), this, SLOT(setLevelDebug(bool)));
connect(m_ui.levelStub, SIGNAL(toggled(bool)), this, SLOT(setLevelStub(bool)));
connect(m_ui.levelInfo, SIGNAL(toggled(bool)), this, SLOT(setLevelInfo(bool)));
connect(m_ui.levelWarn, SIGNAL(toggled(bool)), this, SLOT(setLevelWarn(bool)));
connect(m_ui.levelError, SIGNAL(toggled(bool)), this, SLOT(setLevelError(bool)));
connect(m_ui.levelFatal, SIGNAL(toggled(bool)), this, SLOT(setLevelFatal(bool)));
connect(m_ui.levelGameError, SIGNAL(toggled(bool)), this, SLOT(setLevelGameError(bool)));
connect(m_ui.clear, SIGNAL(clicked()), this, SLOT(clear()));
connect(m_ui.maxLines, SIGNAL(valueChanged(int)), this, SLOT(setMaxLines(int)));
m_logLevel = GBA_LOG_WARN | GBA_LOG_ERROR | GBA_LOG_FATAL;
m_lines = 0;
m_ui.maxLines->setValue(DEFAULT_LINE_LIMIT);
}
void LogView::postLog(int level, const QString& log) {
if (!(level & m_logLevel)) {
return;
}
m_ui.view->appendPlainText(QString("%1:\t%2").arg(toString(level)).arg(log));
++m_lines;
if (m_lines > m_lineLimit) {
clearLine();
}
}
void LogView::clear() {
m_ui.view->clear();
m_lines = 0;
}
void LogView::setLevels(int levels) {
m_logLevel = levels;
m_ui.levelDebug->setCheckState(levels & GBA_LOG_DEBUG ? Qt::Checked : Qt::Unchecked);
m_ui.levelStub->setCheckState(levels & GBA_LOG_STUB ? Qt::Checked : Qt::Unchecked);
m_ui.levelInfo->setCheckState(levels & GBA_LOG_INFO ? Qt::Checked : Qt::Unchecked);
m_ui.levelWarn->setCheckState(levels & GBA_LOG_WARN ? Qt::Checked : Qt::Unchecked);
m_ui.levelError->setCheckState(levels & GBA_LOG_ERROR ? Qt::Checked : Qt::Unchecked);
m_ui.levelFatal->setCheckState(levels & GBA_LOG_FATAL ? Qt::Checked : Qt::Unchecked);
m_ui.levelGameError->setCheckState(levels & GBA_LOG_GAME_ERROR ? Qt::Checked : Qt::Unchecked);
}
void LogView::setLevelDebug(bool set) {
if (set) {
setLevel(GBA_LOG_DEBUG);
} else {
clearLevel(GBA_LOG_DEBUG);
}
}
void LogView::setLevelStub(bool set) {
if (set) {
setLevel(GBA_LOG_STUB);
} else {
clearLevel(GBA_LOG_STUB);
}
}
void LogView::setLevelInfo(bool set) {
if (set) {
setLevel(GBA_LOG_INFO);
} else {
clearLevel(GBA_LOG_INFO);
}
}
void LogView::setLevelWarn(bool set) {
if (set) {
setLevel(GBA_LOG_WARN);
} else {
clearLevel(GBA_LOG_WARN);
}
}
void LogView::setLevelError(bool set) {
if (set) {
setLevel(GBA_LOG_ERROR);
} else {
clearLevel(GBA_LOG_ERROR);
}
}
void LogView::setLevelFatal(bool set) {
if (set) {
setLevel(GBA_LOG_FATAL);
} else {
clearLevel(GBA_LOG_FATAL);
}
}
void LogView::setLevelGameError(bool set) {
if (set) {
setLevel(GBA_LOG_GAME_ERROR);
} else {
clearLevel(GBA_LOG_GAME_ERROR);
}
}
void LogView::setMaxLines(int limit) {
m_lineLimit = limit;
while (m_lines > m_lineLimit) {
clearLine();
}
}
QString LogView::toString(int level) {
switch (level) {
case GBA_LOG_DEBUG:
return tr("DEBUG");
case GBA_LOG_STUB:
return tr("STUB");
case GBA_LOG_INFO:
return tr("INFO");
case GBA_LOG_WARN:
return tr("WARN");
case GBA_LOG_ERROR:
return tr("ERROR");
case GBA_LOG_FATAL:
return tr("FATAL");
case GBA_LOG_GAME_ERROR:
return tr("GAME ERROR");
}
return QString();
}
void LogView::clearLine() {
QTextCursor cursor(m_ui.view->document());
cursor.setPosition(0);
cursor.select(QTextCursor::BlockUnderCursor);
cursor.removeSelectedText();
cursor.deleteChar();
--m_lines;
}

52
src/platform/qt/LogView.h Normal file
View File

@ -0,0 +1,52 @@
#ifndef QGBA_LOG_VIEW
#define QGBA_LOG_VIEW
#include <QWidget>
#include "ui_LogView.h"
extern "C" {
#include "gba-thread.h"
}
namespace QGBA {
class LogView : public QWidget {
Q_OBJECT
public:
LogView(QWidget* parent = nullptr);
public slots:
void postLog(int level, const QString& log);
void setLevels(int levels);
void clear();
void setLevelDebug(bool);
void setLevelStub(bool);
void setLevelInfo(bool);
void setLevelWarn(bool);
void setLevelError(bool);
void setLevelFatal(bool);
void setLevelGameError(bool);
void setMaxLines(int);
private:
static const int DEFAULT_LINE_LIMIT = 1000;
Ui::LogView m_ui;
int m_logLevel;
int m_lines;
int m_lineLimit;
static QString toString(int level);
void setLevel(int level) { m_logLevel |= level; }
void clearLevel(int level) { m_logLevel &= ~level; }
void clearLine();
};
}
#endif

156
src/platform/qt/LogView.ui Normal file
View File

@ -0,0 +1,156 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>LogView</class>
<widget class="QWidget" name="LogView">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>600</width>
<height>400</height>
</rect>
</property>
<property name="windowTitle">
<string>Logs</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QGroupBox" name="groupBox">
<property name="title">
<string>Enabled Levels</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QCheckBox" name="levelDebug">
<property name="text">
<string>Debug</string>
</property>
<property name="checked">
<bool>false</bool>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="levelStub">
<property name="text">
<string>Stub</string>
</property>
<property name="checked">
<bool>false</bool>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="levelInfo">
<property name="text">
<string>Info</string>
</property>
<property name="checked">
<bool>false</bool>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="levelWarn">
<property name="text">
<string>Warning</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="levelError">
<property name="text">
<string>Error</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="levelFatal">
<property name="text">
<string>Fatal</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="Line" name="line">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="levelGameError">
<property name="text">
<string>Game Error</string>
</property>
<property name="checked">
<bool>false</bool>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<spacer name="verticalSpacer_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="clear">
<property name="text">
<string>Clear</string>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_3">
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>Max Lines</string>
</property>
</widget>
</item>
<item>
<widget class="QSpinBox" name="maxLines">
<property name="maximum">
<number>9999</number>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</item>
<item>
<widget class="QPlainTextEdit" name="view">
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

View File

@ -0,0 +1,41 @@
#include "SavestateButton.h"
#include <QApplication>
#include <QPainter>
using namespace QGBA;
SavestateButton::SavestateButton(QWidget* parent)
: QAbstractButton(parent)
{
// Nothing to do
}
void SavestateButton::paintEvent(QPaintEvent*) {
QPainter painter(this);
QRect frame(0, 0, width(), height());
QRect full(1, 1, width() - 2, height() - 2);
QPalette palette = QApplication::palette(this);
painter.setPen(Qt::black);
QLinearGradient grad(0, 0, 0, 1);
grad.setCoordinateMode(QGradient::ObjectBoundingMode);
QColor shadow = palette.color(QPalette::Shadow);
QColor dark = palette.color(QPalette::Dark);
shadow.setAlpha(128);
dark.setAlpha(128);
grad.setColorAt(0, shadow);
grad.setColorAt(1, dark);
painter.setBrush(grad);
painter.drawRect(frame);
painter.setPen(Qt::NoPen);
if (!icon().isNull()) {
painter.drawPixmap(full, icon().pixmap(full.size()));
}
if (hasFocus()) {
QColor highlight = palette.color(QPalette::Highlight);
highlight.setAlpha(128);
painter.fillRect(full, highlight);
}
painter.setPen(QPen(palette.text(), 0));
painter.drawText(full, Qt::AlignCenter, text());
}

View File

@ -0,0 +1,18 @@
#ifndef QGBA_SAVESTATE_BUTTON
#define QGBA_SAVESTATE_BUTTON
#include <QAbstractButton>
namespace QGBA {
class SavestateButton : public QAbstractButton {
public:
SavestateButton(QWidget* parent = nullptr);
protected:
virtual void paintEvent(QPaintEvent*) override;
};
}
#endif

View File

@ -0,0 +1,30 @@
#include "VFileDevice.h"
extern "C" {
#include "util/vfs.h"
}
using namespace QGBA;
VFileDevice::VFileDevice(VFile* vf, QObject* parent)
: QIODevice(parent)
, m_vf(vf)
{
// Nothing to do
}
qint64 VFileDevice::readData(char* data, qint64 maxSize) {
return m_vf->read(m_vf, data, maxSize);
}
qint64 VFileDevice::writeData(const char* data, qint64 maxSize) {
return m_vf->write(m_vf, data, maxSize);
}
qint64 VFileDevice::size() const {
// TODO: Add size method to VFile so this can be actually const
ssize_t pos = m_vf->seek(m_vf, 0, SEEK_CUR);
qint64 size = m_vf->seek(m_vf, 0, SEEK_END);
m_vf->seek(m_vf, pos, SEEK_SET);
return size;
}

View File

@ -0,0 +1,27 @@
#ifndef QGBA_VFILE_DEVICE
#define QGBA_VFILE_DEVICE
#include <QFileDevice>
struct VFile;
namespace QGBA {
class VFileDevice : public QIODevice {
Q_OBJECT
public:
VFileDevice(VFile* vf, QObject* parent = nullptr);
protected:
virtual qint64 readData(char* data, qint64 maxSize) override;
virtual qint64 writeData(const char* data, qint64 maxSize) override;
virtual qint64 size() const override;
private:
mutable VFile* m_vf;
};
}
#endif

View File

@ -0,0 +1,185 @@
#include "VideoView.h"
#ifdef USE_FFMPEG
#include <QFileDialog>
#include <QMap>
using namespace QGBA;
QMap<QString, QString> VideoView::s_acodecMap;
QMap<QString, QString> VideoView::s_vcodecMap;
QMap<QString, QString> VideoView::s_containerMap;
VideoView::VideoView(QWidget* parent)
: QWidget(parent)
, m_audioCodecCstr(nullptr)
, m_videoCodecCstr(nullptr)
, m_containerCstr(nullptr)
{
m_ui.setupUi(this);
if (s_acodecMap.empty()) {
s_acodecMap["aac"] = "libfaac";
s_acodecMap["mp3"] = "libmp3lame";
s_acodecMap["uncompressed"] = "pcm_s16le";
}
if (s_vcodecMap.empty()) {
s_vcodecMap["h264"] = "libx264rgb";
}
if (s_containerMap.empty()) {
s_containerMap["mkv"] = "matroska";
}
connect(m_ui.buttonBox, SIGNAL(rejected()), this, SLOT(close()));
connect(m_ui.start, SIGNAL(clicked()), this, SLOT(startRecording()));
connect(m_ui.stop, SIGNAL(clicked()), this, SLOT(stopRecording()));
connect(m_ui.selectFile, SIGNAL(clicked()), this, SLOT(selectFile()));
connect(m_ui.filename, SIGNAL(textChanged(const QString&)), this, SLOT(setFilename(const QString&)));
connect(m_ui.audio, SIGNAL(activated(const QString&)), this, SLOT(setAudioCodec(const QString&)));
connect(m_ui.video, SIGNAL(activated(const QString&)), this, SLOT(setVideoCodec(const QString&)));
connect(m_ui.container, SIGNAL(activated(const QString&)), this, SLOT(setContainer(const QString&)));
connect(m_ui.abr, SIGNAL(valueChanged(int)), this, SLOT(setAudioBitrate(int)));
connect(m_ui.vbr, SIGNAL(valueChanged(int)), this, SLOT(setVideoBitrate(int)));
FFmpegEncoderInit(&m_encoder);
setAudioCodec(m_ui.audio->currentText());
setVideoCodec(m_ui.video->currentText());
setContainer(m_ui.container->currentText());
}
VideoView::~VideoView() {
stopRecording();
free(m_audioCodecCstr);
free(m_videoCodecCstr);
free(m_containerCstr);
}
void VideoView::startRecording() {
if (!validateSettings()) {
return;
}
if (!FFmpegEncoderOpen(&m_encoder, m_filename.toLocal8Bit().constData())) {
return;
}
m_ui.start->setEnabled(false);
m_ui.stop->setEnabled(true);
emit recordingStarted(&m_encoder.d);
}
void VideoView::stopRecording() {
emit recordingStopped();
FFmpegEncoderClose(&m_encoder);
m_ui.stop->setEnabled(false);
validateSettings();
}
void VideoView::selectFile() {
QString filename = QFileDialog::getSaveFileName(this, tr("Select output file"));
if (!filename.isEmpty()) {
m_ui.filename->setText(filename);
}
}
void VideoView::setFilename(const QString& fname) {
m_filename = fname;
validateSettings();
}
void VideoView::setAudioCodec(const QString& codec) {
free(m_audioCodecCstr);
m_audioCodec = sanitizeCodec(codec);
if (s_acodecMap.contains(m_audioCodec)) {
m_audioCodec = s_acodecMap[m_audioCodec];
}
m_audioCodecCstr = strdup(m_audioCodec.toLocal8Bit().constData());
if (!FFmpegEncoderSetAudio(&m_encoder, m_audioCodecCstr, m_abr)) {
free(m_audioCodecCstr);
m_audioCodecCstr = nullptr;
}
validateSettings();
}
void VideoView::setVideoCodec(const QString& codec) {
free(m_videoCodecCstr);
m_videoCodec = sanitizeCodec(codec);
if (s_vcodecMap.contains(m_videoCodec)) {
m_videoCodec = s_vcodecMap[m_videoCodec];
}
m_videoCodecCstr = strdup(m_videoCodec.toLocal8Bit().constData());
if (!FFmpegEncoderSetVideo(&m_encoder, m_videoCodecCstr, m_vbr)) {
free(m_videoCodecCstr);
m_videoCodecCstr = nullptr;
}
validateSettings();
}
void VideoView::setContainer(const QString& container) {
free(m_containerCstr);
m_container = sanitizeCodec(container);
if (s_containerMap.contains(m_container)) {
m_container = s_containerMap[m_container];
}
m_containerCstr = strdup(m_container.toLocal8Bit().constData());
if (!FFmpegEncoderSetContainer(&m_encoder, m_containerCstr)) {
free(m_containerCstr);
m_containerCstr = nullptr;
}
validateSettings();
}
void VideoView::setAudioBitrate(int br) {
m_abr = br;
FFmpegEncoderSetAudio(&m_encoder, m_audioCodecCstr, m_abr);
validateSettings();
}
void VideoView::setVideoBitrate(int br) {
m_abr = br;
FFmpegEncoderSetVideo(&m_encoder, m_videoCodecCstr, m_vbr);
validateSettings();
}
bool VideoView::validateSettings() {
bool valid = true;
if (!m_audioCodecCstr) {
valid = false;
m_ui.audio->setStyleSheet("QComboBox { color: red; }");
} else {
m_ui.audio->setStyleSheet("");
}
if (!m_videoCodecCstr) {
valid = false;
m_ui.video->setStyleSheet("QComboBox { color: red; }");
} else {
m_ui.video->setStyleSheet("");
}
if (!m_containerCstr) {
valid = false;
m_ui.container->setStyleSheet("QComboBox { color: red; }");
} else {
m_ui.container->setStyleSheet("");
}
// This |valid| check is necessary as if one of the cstrs
// is null, the encoder likely has a dangling pointer
if (valid && !FFmpegEncoderVerifyContainer(&m_encoder)) {
valid = false;
}
m_ui.start->setEnabled(valid && !m_filename.isNull());
return valid;
}
QString VideoView::sanitizeCodec(const QString& codec) {
QString sanitized = codec.toLower();
return sanitized.remove(QChar('.'));
}
#endif

View File

@ -0,0 +1,71 @@
#ifndef QGBA_VIDEO_VIEW
#define QGBA_VIDEO_VIEW
#ifdef USE_FFMPEG
#include <QWidget>
#include "ui_VideoView.h"
extern "C" {
#include "platform/ffmpeg/ffmpeg-encoder.h"
}
namespace QGBA {
class VideoView : public QWidget {
Q_OBJECT
public:
VideoView(QWidget* parent = nullptr);
virtual ~VideoView();
GBAAVStream* getStream() { return &m_encoder.d; }
public slots:
void startRecording();
void stopRecording();
signals:
void recordingStarted(GBAAVStream*);
void recordingStopped();
private slots:
void selectFile();
void setFilename(const QString&);
void setAudioCodec(const QString&);
void setVideoCodec(const QString&);
void setContainer(const QString&);
void setAudioBitrate(int);
void setVideoBitrate(int);
private:
bool validateSettings();
static QString sanitizeCodec(const QString&);
Ui::VideoView m_ui;
FFmpegEncoder m_encoder;
QString m_filename;
QString m_audioCodec;
QString m_videoCodec;
QString m_container;
char* m_audioCodecCstr;
char* m_videoCodecCstr;
char* m_containerCstr;
int m_abr;
int m_vbr;
static QMap<QString, QString> s_acodecMap;
static QMap<QString, QString> s_vcodecMap;
static QMap<QString, QString> s_containerMap;
};
}
#endif
#endif

View File

@ -0,0 +1,256 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>VideoView</class>
<widget class="QWidget" name="VideoView">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>462</width>
<height>194</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="windowTitle">
<string>Record Video</string>
</property>
<layout class="QGridLayout" name="gridLayout_2">
<property name="sizeConstraint">
<enum>QLayout::SetFixedSize</enum>
</property>
<item row="0" column="0" rowspan="2">
<widget class="QGroupBox" name="groupBox_4">
<property name="title">
<string>Format</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_6">
<item>
<widget class="QComboBox" name="container">
<property name="editable">
<bool>true</bool>
</property>
<item>
<property name="text">
<string>MKV</string>
</property>
</item>
<item>
<property name="text">
<string>AVI</string>
</property>
</item>
<item>
<property name="text">
<string>MP4</string>
</property>
</item>
</widget>
</item>
<item>
<widget class="QComboBox" name="video">
<property name="editable">
<bool>true</bool>
</property>
<item>
<property name="text">
<string>PNG</string>
</property>
</item>
<item>
<property name="text">
<string>h.264</string>
</property>
</item>
<item>
<property name="text">
<string>FFV1</string>
</property>
</item>
</widget>
</item>
<item>
<widget class="QComboBox" name="audio">
<property name="editable">
<bool>true</bool>
</property>
<item>
<property name="text">
<string>FLAC</string>
</property>
</item>
<item>
<property name="text">
<string>Vorbis</string>
</property>
</item>
<item>
<property name="text">
<string>MP3</string>
</property>
</item>
<item>
<property name="text">
<string>AAC</string>
</property>
</item>
<item>
<property name="text">
<string>Uncompressed</string>
</property>
</item>
</widget>
</item>
</layout>
</widget>
</item>
<item row="1" column="1">
<widget class="QGroupBox" name="groupBox">
<property name="title">
<string> Bitrate</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>Video</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
<property name="buddy">
<cstring>vbr</cstring>
</property>
</widget>
</item>
<item>
<widget class="QSpinBox" name="vbr">
<property name="suffix">
<string/>
</property>
<property name="minimum">
<number>200</number>
</property>
<property name="maximum">
<number>2000</number>
</property>
<property name="value">
<number>400</number>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_2">
<property name="text">
<string>Audio</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
<property name="buddy">
<cstring>abr</cstring>
</property>
</widget>
</item>
<item>
<widget class="QSpinBox" name="abr">
<property name="minimum">
<number>16</number>
</property>
<property name="maximum">
<number>320</number>
</property>
<property name="value">
<number>192</number>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="0" column="1">
<layout class="QGridLayout" name="gridLayout">
<item row="1" column="0">
<widget class="QPushButton" name="start">
<property name="enabled">
<bool>false</bool>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Start</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QPushButton" name="stop">
<property name="enabled">
<bool>false</bool>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Stop</string>
</property>
</widget>
</item>
<item row="1" column="2">
<widget class="QPushButton" name="selectFile">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Select File</string>
</property>
</widget>
</item>
<item row="0" column="0" colspan="3">
<widget class="QLineEdit" name="filename">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>1</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
</layout>
</item>
<item row="2" column="0" colspan="2">
<widget class="QDialogButtonBox" name="buttonBox">
<property name="standardButtons">
<set>QDialogButtonBox::Close</set>
</property>
</widget>
</item>
</layout>
</widget>
<tabstops>
<tabstop>filename</tabstop>
<tabstop>start</tabstop>
<tabstop>stop</tabstop>
<tabstop>selectFile</tabstop>
<tabstop>container</tabstop>
<tabstop>video</tabstop>
<tabstop>audio</tabstop>
<tabstop>vbr</tabstop>
<tabstop>abr</tabstop>
</tabstops>
<resources/>
<connections/>
</ui>

508
src/platform/qt/Window.cpp Normal file
View File

@ -0,0 +1,508 @@
#include "Window.h"
#include <QFileDialog>
#include <QKeyEvent>
#include <QKeySequence>
#include <QMenuBar>
#include <QStackedLayout>
#include "GameController.h"
#include "GDBController.h"
#include "GDBWindow.h"
#include "LoadSaveState.h"
#include "LogView.h"
#include "VideoView.h"
extern "C" {
#include "platform/commandline.h"
}
using namespace QGBA;
Window::Window(QWidget* parent)
: QMainWindow(parent)
, m_logView(new LogView())
, m_stateWindow(nullptr)
, m_screenWidget(new WindowBackground())
, m_logo(":/res/mgba-1024.png")
#ifdef USE_FFMPEG
, m_videoView(nullptr)
#endif
#ifdef USE_GDB_STUB
, m_gdbController(nullptr)
#endif
{
setWindowTitle(PROJECT_NAME);
m_controller = new GameController(this);
QGLFormat format(QGLFormat(QGL::Rgba | QGL::DoubleBuffer));
format.setSwapInterval(1);
m_display = new Display(format);
m_screenWidget->setMinimumSize(m_display->minimumSize());
m_screenWidget->setSizePolicy(m_display->sizePolicy());
m_screenWidget->setSizeHint(m_display->minimumSize() * 2);
setCentralWidget(m_screenWidget);
connect(m_controller, SIGNAL(gameStarted(GBAThread*)), this, SLOT(gameStarted(GBAThread*)));
connect(m_controller, SIGNAL(gameStopped(GBAThread*)), m_display, SLOT(stopDrawing()));
connect(m_controller, SIGNAL(gameStopped(GBAThread*)), this, SLOT(gameStopped()));
connect(m_controller, SIGNAL(stateLoaded(GBAThread*)), m_display, SLOT(forceDraw()));
connect(m_controller, SIGNAL(postLog(int, const QString&)), m_logView, SLOT(postLog(int, const QString&)));
connect(this, SIGNAL(startDrawing(const uint32_t*, GBAThread*)), m_display, SLOT(startDrawing(const uint32_t*, GBAThread*)), Qt::QueuedConnection);
connect(this, SIGNAL(shutdown()), m_display, SLOT(stopDrawing()));
connect(this, SIGNAL(shutdown()), m_controller, SLOT(closeGame()));
connect(this, SIGNAL(shutdown()), m_logView, SLOT(hide()));
connect(this, SIGNAL(audioBufferSamplesChanged(int)), m_controller, SLOT(setAudioBufferSamples(int)));
connect(this, SIGNAL(fpsTargetChanged(float)), m_controller, SLOT(setFPSTarget(float)));
setupMenu(menuBar());
}
Window::~Window() {
delete m_logView;
delete m_videoView;
}
GBAKey Window::mapKey(int qtKey) {
switch (qtKey) {
case Qt::Key_Z:
return GBA_KEY_A;
break;
case Qt::Key_X:
return GBA_KEY_B;
break;
case Qt::Key_A:
return GBA_KEY_L;
break;
case Qt::Key_S:
return GBA_KEY_R;
break;
case Qt::Key_Return:
return GBA_KEY_START;
break;
case Qt::Key_Backspace:
return GBA_KEY_SELECT;
break;
case Qt::Key_Up:
return GBA_KEY_UP;
break;
case Qt::Key_Down:
return GBA_KEY_DOWN;
break;
case Qt::Key_Left:
return GBA_KEY_LEFT;
break;
case Qt::Key_Right:
return GBA_KEY_RIGHT;
break;
default:
return GBA_KEY_NONE;
}
}
void Window::optionsPassed(StartupOptions* opts) {
if (opts->logLevel) {
m_logView->setLevels(opts->logLevel);
}
if (opts->frameskip) {
m_controller->setFrameskip(opts->frameskip);
}
if (opts->bios) {
m_controller->loadBIOS(opts->bios);
}
if (opts->patch) {
m_controller->loadPatch(opts->patch);
}
if (opts->fname) {
m_controller->loadGame(opts->fname, opts->dirmode);
}
}
void Window::selectROM() {
QString filename = QFileDialog::getOpenFileName(this, tr("Select ROM"));
if (!filename.isEmpty()) {
m_controller->loadGame(filename);
}
}
void Window::selectBIOS() {
QString filename = QFileDialog::getOpenFileName(this, tr("Select BIOS"));
if (!filename.isEmpty()) {
m_controller->loadBIOS(filename);
}
}
void Window::selectPatch() {
QString filename = QFileDialog::getOpenFileName(this, tr("Select patch"), QString(), tr("Patches (*.ips *.ups)"));
if (!filename.isEmpty()) {
m_controller->loadPatch(filename);
}
}
#ifdef USE_FFMPEG
void Window::openVideoWindow() {
if (!m_videoView) {
m_videoView = new VideoView();
connect(m_videoView, SIGNAL(recordingStarted(GBAAVStream*)), m_controller, SLOT(setAVStream(GBAAVStream*)));
connect(m_videoView, SIGNAL(recordingStopped()), m_controller, SLOT(clearAVStream()), Qt::DirectConnection);
connect(m_controller, SIGNAL(gameStopped(GBAThread*)), m_videoView, SLOT(stopRecording()));
connect(m_controller, SIGNAL(gameStopped(GBAThread*)), m_videoView, SLOT(close()));
connect(this, SIGNAL(shutdown()), m_videoView, SLOT(close()));
}
m_videoView->show();
}
#endif
#ifdef USE_GDB_STUB
void Window::gdbOpen() {
if (!m_gdbController) {
m_gdbController = new GDBController(m_controller, this);
}
GDBWindow* window = new GDBWindow(m_gdbController);
window->show();
}
#endif
void Window::keyPressEvent(QKeyEvent* event) {
if (event->isAutoRepeat()) {
QWidget::keyPressEvent(event);
return;
}
if (event->key() == Qt::Key_Tab) {
m_controller->setTurbo(true, false);
}
GBAKey key = mapKey(event->key());
if (key == GBA_KEY_NONE) {
QWidget::keyPressEvent(event);
return;
}
m_controller->keyPressed(key);
event->accept();
}
void Window::keyReleaseEvent(QKeyEvent* event) {
if (event->isAutoRepeat()) {
QWidget::keyReleaseEvent(event);
return;
}
if (event->key() == Qt::Key_Tab) {
m_controller->setTurbo(false, false);
}
GBAKey key = mapKey(event->key());
if (key == GBA_KEY_NONE) {
QWidget::keyPressEvent(event);
return;
}
m_controller->keyReleased(key);
event->accept();
}
void Window::resizeEvent(QResizeEvent*) {
redoLogo();
}
void Window::closeEvent(QCloseEvent* event) {
emit shutdown();
QMainWindow::closeEvent(event);
}
void Window::toggleFullScreen() {
if (isFullScreen()) {
showNormal();
} else {
showFullScreen();
}
}
void Window::gameStarted(GBAThread* context) {
emit startDrawing(m_controller->drawContext(), context);
foreach (QAction* action, m_gameActions) {
action->setDisabled(false);
}
char title[13] = { '\0' };
GBAGetGameTitle(context->gba, title);
setWindowTitle(tr(PROJECT_NAME " - %1").arg(title));
attachWidget(m_display);
m_screenWidget->setScaledContents(true);
}
void Window::gameStopped() {
foreach (QAction* action, m_gameActions) {
action->setDisabled(true);
}
setWindowTitle(tr(PROJECT_NAME));
detachWidget(m_display);
m_screenWidget->setScaledContents(false);
redoLogo();
}
void Window::redoLogo() {
if (m_controller->isLoaded()) {
return;
}
QPixmap logo(m_logo.scaled(m_screenWidget->size() * m_screenWidget->devicePixelRatio(), Qt::KeepAspectRatio, Qt::SmoothTransformation));
logo.setDevicePixelRatio(m_screenWidget->devicePixelRatio());
m_screenWidget->setPixmap(logo);
}
void Window::openStateWindow(LoadSave ls) {
if (m_stateWindow) {
return;
}
bool wasPaused = m_controller->isPaused();
m_stateWindow = new LoadSaveState(m_controller);
connect(this, SIGNAL(shutdown()), m_stateWindow, SLOT(close()));
connect(m_controller, SIGNAL(gameStopped(GBAThread*)), m_stateWindow, SLOT(close()));
connect(m_stateWindow, &LoadSaveState::closed, [this]() {
m_screenWidget->layout()->removeWidget(m_stateWindow);
m_stateWindow = nullptr;
setFocus();
});
if (!wasPaused) {
m_controller->setPaused(true);
connect(m_stateWindow, &LoadSaveState::closed, [this]() { m_controller->setPaused(false); });
}
m_stateWindow->setAttribute(Qt::WA_DeleteOnClose);
m_stateWindow->setMode(ls);
attachWidget(m_stateWindow);
}
void Window::setupMenu(QMenuBar* menubar) {
menubar->clear();
QMenu* fileMenu = menubar->addMenu(tr("&File"));
fileMenu->addAction(tr("Load &ROM..."), this, SLOT(selectROM()), QKeySequence::Open);
fileMenu->addAction(tr("Load &BIOS..."), this, SLOT(selectBIOS()));
fileMenu->addAction(tr("Load &patch..."), this, SLOT(selectPatch()));
fileMenu->addSeparator();
QAction* loadState = new QAction(tr("&Load state"), fileMenu);
loadState->setShortcut(tr("Ctrl+L"));
connect(loadState, &QAction::triggered, [this]() { this->openStateWindow(LoadSave::LOAD); });
m_gameActions.append(loadState);
fileMenu->addAction(loadState);
QAction* saveState = new QAction(tr("&Save state"), fileMenu);
saveState->setShortcut(tr("Ctrl+S"));
connect(saveState, &QAction::triggered, [this]() { this->openStateWindow(LoadSave::SAVE); });
m_gameActions.append(saveState);
fileMenu->addAction(saveState);
QMenu* quickLoadMenu = fileMenu->addMenu(tr("Quick load"));
QMenu* quickSaveMenu = fileMenu->addMenu(tr("Quick save"));
int i;
for (i = 1; i < 10; ++i) {
QAction* quickLoad = new QAction(tr("State &%1").arg(i), quickLoadMenu);
quickLoad->setShortcut(tr("F%1").arg(i));
connect(quickLoad, &QAction::triggered, [this, i]() { m_controller->loadState(i); });
m_gameActions.append(quickLoad);
quickLoadMenu->addAction(quickLoad);
QAction* quickSave = new QAction(tr("State &%1").arg(i), quickSaveMenu);
quickSave->setShortcut(tr("Shift+F%1").arg(i));
connect(quickSave, &QAction::triggered, [this, i]() { m_controller->saveState(i); });
m_gameActions.append(quickSave);
quickSaveMenu->addAction(quickSave);
}
#if defined(USE_PNG) || defined(USE_FFMPEG)
fileMenu->addSeparator();
#endif
#ifdef USE_PNG
QAction* screenshot = new QAction(tr("Take &screenshot"), fileMenu);
screenshot->setShortcut(tr("F12"));
connect(screenshot, SIGNAL(triggered()), m_display, SLOT(screenshot()));
m_gameActions.append(screenshot);
fileMenu->addAction(screenshot);
#endif
#ifdef USE_FFMPEG
QAction* recordOutput = new QAction(tr("Record output..."), fileMenu);
recordOutput->setShortcut(tr("F11"));
connect(recordOutput, SIGNAL(triggered()), this, SLOT(openVideoWindow()));
fileMenu->addAction(recordOutput);
#endif
#ifndef Q_OS_MAC
fileMenu->addSeparator();
fileMenu->addAction(tr("E&xit"), this, SLOT(close()), QKeySequence::Quit);
#endif
QMenu* emulationMenu = menubar->addMenu(tr("&Emulation"));
QAction* reset = new QAction(tr("&Reset"), emulationMenu);
reset->setShortcut(tr("Ctrl+R"));
connect(reset, SIGNAL(triggered()), m_controller, SLOT(reset()));
m_gameActions.append(reset);
emulationMenu->addAction(reset);
QAction* shutdown = new QAction(tr("Sh&utdown"), emulationMenu);
connect(shutdown, SIGNAL(triggered()), m_controller, SLOT(closeGame()));
m_gameActions.append(shutdown);
emulationMenu->addAction(shutdown);
emulationMenu->addSeparator();
QAction* pause = new QAction(tr("&Pause"), emulationMenu);
pause->setChecked(false);
pause->setCheckable(true);
pause->setShortcut(tr("Ctrl+P"));
connect(pause, SIGNAL(triggered(bool)), m_controller, SLOT(setPaused(bool)));
connect(m_controller, &GameController::gamePaused, [this, pause]() {
pause->setChecked(true);
QImage currentImage(reinterpret_cast<const uchar*>(m_controller->drawContext()), VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS, 1024, QImage::Format_RGB32);
QPixmap pixmap;
pixmap.convertFromImage(currentImage.rgbSwapped());
m_screenWidget->setPixmap(pixmap);
});
connect(m_controller, &GameController::gameUnpaused, [pause]() { pause->setChecked(false); });
m_gameActions.append(pause);
emulationMenu->addAction(pause);
QAction* frameAdvance = new QAction(tr("&Next frame"), emulationMenu);
frameAdvance->setShortcut(tr("Ctrl+N"));
connect(frameAdvance, SIGNAL(triggered()), m_controller, SLOT(frameAdvance()));
m_gameActions.append(frameAdvance);
emulationMenu->addAction(frameAdvance);
QMenu* target = emulationMenu->addMenu("FPS target");
QAction* setTarget = new QAction(tr("15"), emulationMenu);
connect(setTarget, &QAction::triggered, [this]() { emit fpsTargetChanged(15); });
target->addAction(setTarget);
setTarget = new QAction(tr("30"), emulationMenu);
connect(setTarget, &QAction::triggered, [this]() { emit fpsTargetChanged(30); });
target->addAction(setTarget);
setTarget = new QAction(tr("45"), emulationMenu);
connect(setTarget, &QAction::triggered, [this]() { emit fpsTargetChanged(45); });
target->addAction(setTarget);
setTarget = new QAction(tr("60"), emulationMenu);
connect(setTarget, &QAction::triggered, [this]() { emit fpsTargetChanged(60); });
target->addAction(setTarget);
setTarget = new QAction(tr("90"), emulationMenu);
connect(setTarget, &QAction::triggered, [this]() { emit fpsTargetChanged(90); });
target->addAction(setTarget);
setTarget = new QAction(tr("120"), emulationMenu);
connect(setTarget, &QAction::triggered, [this]() { emit fpsTargetChanged(120); });
target->addAction(setTarget);
setTarget = new QAction(tr("240"), emulationMenu);
connect(setTarget, &QAction::triggered, [this]() { emit fpsTargetChanged(240); });
target->addAction(setTarget);
emulationMenu->addSeparator();
QAction* turbo = new QAction(tr("T&urbo"), emulationMenu);
turbo->setCheckable(true);
turbo->setChecked(false);
turbo->setShortcut(tr("Shift+Tab"));
connect(turbo, SIGNAL(triggered(bool)), m_controller, SLOT(setTurbo(bool)));
emulationMenu->addAction(turbo);
QAction* videoSync = new QAction(tr("Sync to &video"), emulationMenu);
videoSync->setCheckable(true);
videoSync->setChecked(GameController::VIDEO_SYNC);
connect(videoSync, SIGNAL(triggered(bool)), m_controller, SLOT(setVideoSync(bool)));
emulationMenu->addAction(videoSync);
QAction* audioSync = new QAction(tr("Sync to &audio"), emulationMenu);
audioSync->setCheckable(true);
audioSync->setChecked(GameController::AUDIO_SYNC);
connect(audioSync, SIGNAL(triggered(bool)), m_controller, SLOT(setAudioSync(bool)));
emulationMenu->addAction(audioSync);
QMenu* videoMenu = menubar->addMenu(tr("&Video"));
QMenu* frameMenu = videoMenu->addMenu(tr("Frame size"));
QAction* setSize = new QAction(tr("1x"), videoMenu);
connect(setSize, &QAction::triggered, [this]() {
showNormal();
resize(VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS);
});
frameMenu->addAction(setSize);
setSize = new QAction(tr("2x"), videoMenu);
connect(setSize, &QAction::triggered, [this]() {
showNormal();
resize(VIDEO_HORIZONTAL_PIXELS * 2, VIDEO_VERTICAL_PIXELS * 2);
});
frameMenu->addAction(setSize);
setSize = new QAction(tr("3x"), videoMenu);
connect(setSize, &QAction::triggered, [this]() {
showNormal();
resize(VIDEO_HORIZONTAL_PIXELS * 3, VIDEO_VERTICAL_PIXELS * 3);
});
frameMenu->addAction(setSize);
setSize = new QAction(tr("4x"), videoMenu);
connect(setSize, &QAction::triggered, [this]() {
showNormal();
resize(VIDEO_HORIZONTAL_PIXELS * 4, VIDEO_VERTICAL_PIXELS * 4);
});
frameMenu->addAction(setSize);
frameMenu->addAction(tr("Fullscreen"), this, SLOT(toggleFullScreen()), QKeySequence("Ctrl+F"));
QMenu* skipMenu = videoMenu->addMenu(tr("Frame&skip"));
for (int i = 0; i <= 10; ++i) {
QAction* setSkip = new QAction(QString::number(i), skipMenu);
connect(setSkip, &QAction::triggered, [this, i]() {
m_controller->setFrameskip(i);
});
skipMenu->addAction(setSkip);
}
QMenu* soundMenu = menubar->addMenu(tr("&Sound"));
QMenu* buffersMenu = soundMenu->addMenu(tr("Buffer &size"));
QAction* setBuffer = new QAction(tr("512"), buffersMenu);
connect(setBuffer, &QAction::triggered, [this]() { emit audioBufferSamplesChanged(512); });
buffersMenu->addAction(setBuffer);
setBuffer = new QAction(tr("1024"), buffersMenu);
connect(setBuffer, &QAction::triggered, [this]() { emit audioBufferSamplesChanged(1024); });
buffersMenu->addAction(setBuffer);
setBuffer = new QAction(tr("2048"), buffersMenu);
connect(setBuffer, &QAction::triggered, [this]() { emit audioBufferSamplesChanged(2048); });
buffersMenu->addAction(setBuffer);
QMenu* debuggingMenu = menubar->addMenu(tr("&Debugging"));
QAction* viewLogs = new QAction(tr("View &logs..."), debuggingMenu);
connect(viewLogs, SIGNAL(triggered()), m_logView, SLOT(show()));
debuggingMenu->addAction(viewLogs);
#ifdef USE_GDB_STUB
QAction* gdbWindow = new QAction(tr("Start &GDB server..."), debuggingMenu);
connect(gdbWindow, SIGNAL(triggered()), this, SLOT(gdbOpen()));
debuggingMenu->addAction(gdbWindow);
#endif
foreach (QAction* action, m_gameActions) {
action->setDisabled(true);
}
}
void Window::attachWidget(QWidget* widget) {
m_screenWidget->layout()->addWidget(widget);
static_cast<QStackedLayout*>(m_screenWidget->layout())->setCurrentWidget(widget);
}
void Window::detachWidget(QWidget* widget) {
m_screenWidget->layout()->removeWidget(widget);
}
WindowBackground::WindowBackground(QWidget* parent)
: QLabel(parent)
{
setLayout(new QStackedLayout());
layout()->setContentsMargins(0, 0, 0, 0);
setAlignment(Qt::AlignCenter);
QPalette p = palette();
p.setColor(backgroundRole(), Qt::black);
setPalette(p);
setAutoFillBackground(true);
}
void WindowBackground::setSizeHint(const QSize& hint) {
m_sizeHint = hint;
}
QSize WindowBackground::sizeHint() const {
return m_sizeHint;
}

107
src/platform/qt/Window.h Normal file
View File

@ -0,0 +1,107 @@
#ifndef QGBA_WINDOW
#define QGBA_WINDOW
#include <QAudioOutput>
#include <QMainWindow>
extern "C" {
#include "gba.h"
}
#include "GDBController.h"
#include "Display.h"
#include "LoadSaveState.h"
struct StartupOptions;
namespace QGBA {
class GameController;
class LogView;
class VideoView;
class WindowBackground;
class Window : public QMainWindow {
Q_OBJECT
public:
Window(QWidget* parent = nullptr);
virtual ~Window();
GameController* controller() { return m_controller; }
static GBAKey mapKey(int qtKey);
void optionsPassed(StartupOptions*);
signals:
void startDrawing(const uint32_t*, GBAThread*);
void shutdown();
void audioBufferSamplesChanged(int samples);
void fpsTargetChanged(float target);
public slots:
void selectROM();
void selectBIOS();
void selectPatch();
void toggleFullScreen();
#ifdef USE_FFMPEG
void openVideoWindow();
#endif
#ifdef USE_GDB_STUB
void gdbOpen();
#endif
protected:
virtual void keyPressEvent(QKeyEvent* event) override;
virtual void keyReleaseEvent(QKeyEvent* event) override;
virtual void resizeEvent(QResizeEvent*) override;
virtual void closeEvent(QCloseEvent*) override;
private slots:
void gameStarted(GBAThread*);
void gameStopped();
void redoLogo();
private:
void setupMenu(QMenuBar*);
void openStateWindow(LoadSave);
void attachWidget(QWidget* widget);
void detachWidget(QWidget* widget);
GameController* m_controller;
Display* m_display;
QList<QAction*> m_gameActions;
LogView* m_logView;
LoadSaveState* m_stateWindow;
WindowBackground* m_screenWidget;
QPixmap m_logo;
#ifdef USE_FFMPEG
VideoView* m_videoView;
#endif
#ifdef USE_GDB_STUB
GDBController* m_gdbController;
#endif
};
class WindowBackground : public QLabel {
Q_OBJECT
public:
WindowBackground(QWidget* parent = 0);
void setSizeHint(const QSize& size);
virtual QSize sizeHint() const override;
private:
QSize m_sizeHint;
};
}
#endif

7
src/platform/qt/main.cpp Normal file
View File

@ -0,0 +1,7 @@
#include "GBAApp.h"
#include "Window.h"
int main(int argc, char* argv[]) {
QGBA::GBAApp application(argc, argv);
return application.exec();
}

View File

@ -0,0 +1,5 @@
<!DOCTYPE RCC><RCC version="1.0">
<qresource>
<file>../../../res/mgba-1024.png</file>
</qresource>
</RCC>

View File

@ -48,10 +48,9 @@ static inline Socket SocketOpenTCP(int port, uint32_t bindAddress) {
struct sockaddr_in bindInfo = {
.sin_family = AF_INET,
.sin_port = htons(port),
.sin_addr = {
.s_addr = htonl(bindAddress)
}
.sin_addr = { 0 }
};
bindInfo.sin_addr.s_addr = htonl(bindAddress);
int err = bind(sock, (const struct sockaddr*) &bindInfo, sizeof(struct sockaddr_in));
if (err) {
close(sock);
@ -69,10 +68,9 @@ static inline Socket SocketConnectTCP(int port, uint32_t destinationAddress) {
struct sockaddr_in bindInfo = {
.sin_family = AF_INET,
.sin_port = htons(port),
.sin_addr = {
.s_addr = htonl(destinationAddress)
}
.sin_addr = { 0 }
};
bindInfo.sin_addr.s_addr = htonl(destinationAddress);
int err = connect(sock, (const struct sockaddr*) &bindInfo, sizeof(struct sockaddr_in));
if (err) {
close(sock);