mirror of
https://github.com/melonDS-emu/melonDS.git
synced 2025-07-30 01:29:52 -06:00
Merge branch 'master' into no-more-context-mess
This commit is contained in:
@ -67,6 +67,20 @@ AudioSettingsDialog::AudioSettingsDialog(QWidget* parent) : QDialog(parent), ui(
|
||||
bool iswav = (Config::MicInputType == 3);
|
||||
ui->txtMicWavPath->setEnabled(iswav);
|
||||
ui->btnMicWavBrowse->setEnabled(iswav);
|
||||
|
||||
int inst = Platform::InstanceID();
|
||||
if (inst > 0)
|
||||
{
|
||||
ui->lblInstanceNum->setText(QString("Configuring settings for instance %1").arg(inst+1));
|
||||
ui->cbInterpolation->setEnabled(false);
|
||||
ui->cbBitrate->setEnabled(false);
|
||||
for (QAbstractButton* btn : grpMicMode->buttons())
|
||||
btn->setEnabled(false);
|
||||
ui->txtMicWavPath->setEnabled(false);
|
||||
ui->btnMicWavBrowse->setEnabled(false);
|
||||
}
|
||||
else
|
||||
ui->lblInstanceNum->hide();
|
||||
}
|
||||
|
||||
AudioSettingsDialog::~AudioSettingsDialog()
|
||||
|
@ -7,7 +7,7 @@
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>482</width>
|
||||
<height>256</height>
|
||||
<height>301</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
@ -23,6 +23,13 @@
|
||||
<property name="sizeConstraint">
|
||||
<enum>QLayout::SetFixedSize</enum>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QLabel" name="lblInstanceNum">
|
||||
<property name="text">
|
||||
<string>Configuring settings for instance X</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox">
|
||||
<property name="title">
|
||||
@ -76,7 +83,7 @@
|
||||
<item row="1" column="1">
|
||||
<widget class="QComboBox" name="cbBitrate">
|
||||
<property name="whatsThis">
|
||||
<string><html><head/><body><p>The bitrate of audio playback. If set to "Automatic" this will be 10-bit for DS mode and 16-bit for DSi mode.</p></body></html></string>
|
||||
<string><html><head/><body><p>The bitrate of audio playback. If set to "Automatic" this will be 10-bit for DS mode and 16-bit for DSi mode.</p></body></html></string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
|
@ -14,9 +14,11 @@ set(SOURCES_QT_SDL
|
||||
InputConfig/MapButton.h
|
||||
InputConfig/resources/ds.qrc
|
||||
VideoSettingsDialog.cpp
|
||||
CameraSettingsDialog.cpp
|
||||
AudioSettingsDialog.cpp
|
||||
FirmwareSettingsDialog.cpp
|
||||
PathSettingsDialog.cpp
|
||||
MPSettingsDialog.cpp
|
||||
WifiSettingsDialog.cpp
|
||||
InterfaceSettingsDialog.cpp
|
||||
ROMInfoDialog.cpp
|
||||
@ -25,14 +27,16 @@ set(SOURCES_QT_SDL
|
||||
Input.cpp
|
||||
LAN_PCap.cpp
|
||||
LAN_Socket.cpp
|
||||
LocalMP.cpp
|
||||
OSD.cpp
|
||||
OSD_shaders.h
|
||||
font.h
|
||||
Platform.cpp
|
||||
QPathInput.h
|
||||
ROMManager.cpp
|
||||
SaveManager.cpp
|
||||
|
||||
SaveManager.cpp
|
||||
CameraManager.cpp
|
||||
|
||||
ArchiveUtil.h
|
||||
ArchiveUtil.cpp
|
||||
|
||||
@ -59,11 +63,11 @@ if (WIN32)
|
||||
endif()
|
||||
|
||||
if (USE_QT6)
|
||||
find_package(Qt6 COMPONENTS Core Gui Widgets Network OpenGL OpenGLWidgets REQUIRED)
|
||||
set(QT_LINK_LIBS Qt6::Core Qt6::Gui Qt6::Widgets Qt6::Network Qt6::OpenGL Qt6::OpenGLWidgets)
|
||||
find_package(Qt6 COMPONENTS Core Gui Widgets Network Multimedia OpenGL OpenGLWidgets REQUIRED)
|
||||
set(QT_LINK_LIBS Qt6::Core Qt6::Gui Qt6::Widgets Qt6::Network Qt6::Multimedia Qt6::OpenGL Qt6::OpenGLWidgets)
|
||||
else()
|
||||
find_package(Qt5 COMPONENTS Core Gui Widgets Network REQUIRED)
|
||||
set(QT_LINK_LIBS Qt5::Core Qt5::Gui Qt5::Widgets Qt5::Network)
|
||||
find_package(Qt5 COMPONENTS Core Gui Widgets Network Multimedia REQUIRED)
|
||||
set(QT_LINK_LIBS Qt5::Core Qt5::Gui Qt5::Widgets Qt5::Network Qt5::Multimedia)
|
||||
endif()
|
||||
|
||||
set(CMAKE_AUTOMOC ON)
|
||||
@ -157,6 +161,8 @@ if (PORTABLE)
|
||||
endif()
|
||||
|
||||
if (APPLE)
|
||||
target_sources(melonDS PRIVATE sem_timedwait.cpp)
|
||||
|
||||
# Copy icon into the bundle
|
||||
set(RESOURCE_FILES "${CMAKE_SOURCE_DIR}/res/melon.icns")
|
||||
target_sources(melonDS PUBLIC "${RESOURCE_FILES}")
|
||||
|
612
src/frontend/qt_sdl/CameraManager.cpp
Normal file
612
src/frontend/qt_sdl/CameraManager.cpp
Normal file
@ -0,0 +1,612 @@
|
||||
/*
|
||||
Copyright 2016-2022 melonDS team
|
||||
|
||||
This file is part of melonDS.
|
||||
|
||||
melonDS 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 3 of the License, or (at your option)
|
||||
any later version.
|
||||
|
||||
melonDS 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 melonDS. If not, see http://www.gnu.org/licenses/.
|
||||
*/
|
||||
|
||||
#include "CameraManager.h"
|
||||
#include "Config.h"
|
||||
|
||||
|
||||
#if QT_VERSION >= 0x060000
|
||||
|
||||
CameraFrameDumper::CameraFrameDumper(QObject* parent) : QVideoSink(parent)
|
||||
{
|
||||
cam = (CameraManager*)parent;
|
||||
|
||||
connect(this, &CameraFrameDumper::videoFrameChanged, this, &CameraFrameDumper::present);
|
||||
}
|
||||
|
||||
void CameraFrameDumper::present(const QVideoFrame& _frame)
|
||||
{
|
||||
QVideoFrame frame(_frame);
|
||||
if (!frame.map(QVideoFrame::ReadOnly))
|
||||
return;
|
||||
if (!frame.isReadable())
|
||||
{
|
||||
frame.unmap();
|
||||
return;
|
||||
}
|
||||
|
||||
switch (frame.pixelFormat())
|
||||
{
|
||||
case QVideoFrameFormat::Format_XRGB8888:
|
||||
case QVideoFrameFormat::Format_YUYV:
|
||||
cam->feedFrame((u32*)frame.bits(0), frame.width(), frame.height(), frame.pixelFormat() == QVideoFrameFormat::Format_YUYV);
|
||||
break;
|
||||
|
||||
case QVideoFrameFormat::Format_UYVY:
|
||||
cam->feedFrame_UYVY((u32*)frame.bits(0), frame.width(), frame.height());
|
||||
break;
|
||||
|
||||
case QVideoFrameFormat::Format_NV12:
|
||||
cam->feedFrame_NV12((u8*)frame.bits(0), (u8*)frame.bits(1), frame.width(), frame.height());
|
||||
break;
|
||||
}
|
||||
|
||||
frame.unmap();
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
CameraFrameDumper::CameraFrameDumper(QObject* parent) : QAbstractVideoSurface(parent)
|
||||
{
|
||||
cam = (CameraManager*)parent;
|
||||
}
|
||||
|
||||
bool CameraFrameDumper::present(const QVideoFrame& _frame)
|
||||
{
|
||||
QVideoFrame frame(_frame);
|
||||
if (!frame.map(QAbstractVideoBuffer::ReadOnly))
|
||||
return false;
|
||||
if (!frame.isReadable())
|
||||
{
|
||||
frame.unmap();
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (frame.pixelFormat())
|
||||
{
|
||||
case QVideoFrame::Format_RGB32:
|
||||
case QVideoFrame::Format_YUYV:
|
||||
cam->feedFrame((u32*)frame.bits(0), frame.width(), frame.height(), frame.pixelFormat() == QVideoFrame::Format_YUYV);
|
||||
break;
|
||||
|
||||
case QVideoFrame::Format_UYVY:
|
||||
cam->feedFrame_UYVY((u32*)frame.bits(0), frame.width(), frame.height());
|
||||
break;
|
||||
|
||||
case QVideoFrame::Format_NV12:
|
||||
cam->feedFrame_NV12((u8*)frame.bits(0), (u8*)frame.bits(1), frame.width(), frame.height());
|
||||
break;
|
||||
}
|
||||
|
||||
frame.unmap();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
QList<QVideoFrame::PixelFormat> CameraFrameDumper::supportedPixelFormats(QAbstractVideoBuffer::HandleType type) const
|
||||
{
|
||||
QList<QVideoFrame::PixelFormat> ret;
|
||||
|
||||
ret.append(QVideoFrame::Format_RGB32);
|
||||
ret.append(QVideoFrame::Format_YUYV);
|
||||
ret.append(QVideoFrame::Format_UYVY);
|
||||
ret.append(QVideoFrame::Format_NV12);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
CameraManager::CameraManager(int num, int width, int height, bool yuv) : QObject()
|
||||
{
|
||||
this->num = num;
|
||||
|
||||
startNum = 0;
|
||||
|
||||
// QCamera needs to be controlled from the UI thread, hence this
|
||||
connect(this, SIGNAL(camStartSignal()), this, SLOT(camStart()));
|
||||
connect(this, SIGNAL(camStopSignal()), this, SLOT(camStop()));
|
||||
|
||||
frameWidth = width;
|
||||
frameHeight = height;
|
||||
frameFormatYUV = yuv;
|
||||
|
||||
int fbsize = frameWidth * frameHeight;
|
||||
if (yuv) fbsize /= 2;
|
||||
frameBuffer = new u32[fbsize];
|
||||
tempFrameBuffer = new u32[fbsize];
|
||||
|
||||
inputType = -1;
|
||||
xFlip = false;
|
||||
init();
|
||||
}
|
||||
|
||||
CameraManager::~CameraManager()
|
||||
{
|
||||
deInit();
|
||||
|
||||
// save settings here?
|
||||
|
||||
delete[] frameBuffer;
|
||||
}
|
||||
|
||||
void CameraManager::init()
|
||||
{
|
||||
if (inputType != -1)
|
||||
deInit();
|
||||
|
||||
startNum = 0;
|
||||
|
||||
inputType = Config::Camera[num].InputType;
|
||||
imagePath = QString::fromStdString(Config::Camera[num].ImagePath);
|
||||
camDeviceName = QString::fromStdString(Config::Camera[num].CamDeviceName);
|
||||
|
||||
camDevice = nullptr;
|
||||
|
||||
{
|
||||
// fill the framebuffer with black
|
||||
|
||||
int total = frameWidth * frameHeight;
|
||||
u32 fill = 0;
|
||||
if (frameFormatYUV)
|
||||
{
|
||||
total /= 2;
|
||||
fill = 0x80008000;
|
||||
}
|
||||
|
||||
for (int i = 0; i < total; i++)
|
||||
frameBuffer[i] = fill;
|
||||
}
|
||||
|
||||
if (inputType == 1)
|
||||
{
|
||||
// still image
|
||||
|
||||
QImage img(imagePath);
|
||||
if (!img.isNull())
|
||||
{
|
||||
QImage imgconv = img.convertToFormat(QImage::Format_RGB32);
|
||||
if (frameFormatYUV)
|
||||
{
|
||||
copyFrame_RGBtoYUV((u32*)img.bits(), img.width(), img.height(),
|
||||
frameBuffer, frameWidth, frameHeight,
|
||||
false);
|
||||
}
|
||||
else
|
||||
{
|
||||
copyFrame_Straight((u32*)img.bits(), img.width(), img.height(),
|
||||
frameBuffer, frameWidth, frameHeight,
|
||||
false, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (inputType == 2)
|
||||
{
|
||||
// physical camera
|
||||
|
||||
#if QT_VERSION >= 0x060000
|
||||
const QList<QCameraDevice> cameras = QMediaDevices::videoInputs();
|
||||
for (const QCameraDevice& cam : cameras)
|
||||
{
|
||||
if (QString(cam.id()) == camDeviceName)
|
||||
{
|
||||
camDevice = new QCamera(cam);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (camDevice)
|
||||
{
|
||||
const QList<QCameraFormat> supported = camDevice->cameraDevice().videoFormats();
|
||||
bool good = false;
|
||||
for (const QCameraFormat& item : supported)
|
||||
{
|
||||
if (item.pixelFormat() != QVideoFrameFormat::Format_YUYV &&
|
||||
item.pixelFormat() != QVideoFrameFormat::Format_UYVY &&
|
||||
item.pixelFormat() != QVideoFrameFormat::Format_NV12 &&
|
||||
item.pixelFormat() != QVideoFrameFormat::Format_XRGB8888)
|
||||
continue;
|
||||
|
||||
if (item.resolution().width() != 640 && item.resolution().height() != 480)
|
||||
continue;
|
||||
|
||||
camDevice->setCameraFormat(item);
|
||||
good = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!good)
|
||||
{
|
||||
delete camDevice;
|
||||
camDevice = nullptr;
|
||||
}
|
||||
else
|
||||
{
|
||||
camDumper = new CameraFrameDumper(this);
|
||||
|
||||
camSession = new QMediaCaptureSession(this);
|
||||
camSession->setCamera(camDevice);
|
||||
camSession->setVideoOutput(camDumper);
|
||||
}
|
||||
}
|
||||
#else
|
||||
camDevice = new QCamera(camDeviceName.toUtf8());
|
||||
if (camDevice->error() != QCamera::NoError)
|
||||
{
|
||||
delete camDevice;
|
||||
camDevice = nullptr;
|
||||
}
|
||||
|
||||
if (camDevice)
|
||||
{
|
||||
camDevice->load();
|
||||
|
||||
const QList<QCameraViewfinderSettings> supported = camDevice->supportedViewfinderSettings();
|
||||
bool good = false;
|
||||
for (const QCameraViewfinderSettings& item : supported)
|
||||
{
|
||||
if (item.pixelFormat() != QVideoFrame::Format_YUYV &&
|
||||
item.pixelFormat() != QVideoFrame::Format_UYVY &&
|
||||
item.pixelFormat() != QVideoFrame::Format_NV12 &&
|
||||
item.pixelFormat() != QVideoFrame::Format_RGB32)
|
||||
continue;
|
||||
|
||||
if (item.resolution().width() != 640 && item.resolution().height() != 480)
|
||||
continue;
|
||||
|
||||
camDevice->setViewfinderSettings(item);
|
||||
good = true;
|
||||
break;
|
||||
}
|
||||
|
||||
camDevice->unload();
|
||||
|
||||
if (!good)
|
||||
{
|
||||
delete camDevice;
|
||||
camDevice = nullptr;
|
||||
}
|
||||
else
|
||||
{
|
||||
camDumper = new CameraFrameDumper(this);
|
||||
camDevice->setViewfinder(camDumper);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
void CameraManager::deInit()
|
||||
{
|
||||
if (inputType == 2)
|
||||
{
|
||||
if (camDevice)
|
||||
{
|
||||
camDevice->stop();
|
||||
delete camDevice;
|
||||
delete camDumper;
|
||||
#if QT_VERSION >= 0x060000
|
||||
delete camSession;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
camDevice = nullptr;
|
||||
inputType = -1;
|
||||
}
|
||||
|
||||
void CameraManager::start()
|
||||
{
|
||||
if (startNum == 1) return;
|
||||
startNum = 1;
|
||||
|
||||
if (inputType == 2)
|
||||
{
|
||||
emit camStartSignal();
|
||||
}
|
||||
}
|
||||
|
||||
void CameraManager::stop()
|
||||
{
|
||||
if (startNum == 0) return;
|
||||
startNum = 0;
|
||||
|
||||
if (inputType == 2)
|
||||
{
|
||||
emit camStopSignal();
|
||||
}
|
||||
}
|
||||
|
||||
bool CameraManager::isStarted()
|
||||
{
|
||||
return startNum != 0;
|
||||
}
|
||||
|
||||
void CameraManager::camStart()
|
||||
{
|
||||
if (camDevice)
|
||||
camDevice->start();
|
||||
}
|
||||
|
||||
void CameraManager::camStop()
|
||||
{
|
||||
if (camDevice)
|
||||
camDevice->stop();
|
||||
}
|
||||
|
||||
void CameraManager::setXFlip(bool flip)
|
||||
{
|
||||
xFlip = flip;
|
||||
}
|
||||
|
||||
void CameraManager::captureFrame(u32* frame, int width, int height, bool yuv)
|
||||
{
|
||||
frameMutex.lock();
|
||||
|
||||
if ((width == frameWidth) &&
|
||||
(height == frameHeight) &&
|
||||
(yuv == frameFormatYUV) &&
|
||||
(!xFlip))
|
||||
{
|
||||
int len = width * height;
|
||||
if (yuv) len /= 2;
|
||||
memcpy(frame, frameBuffer, len * sizeof(u32));
|
||||
}
|
||||
else
|
||||
{
|
||||
if (yuv == frameFormatYUV)
|
||||
{
|
||||
copyFrame_Straight(frameBuffer, frameWidth, frameHeight,
|
||||
frame, width, height,
|
||||
xFlip, yuv);
|
||||
}
|
||||
else if (yuv)
|
||||
{
|
||||
copyFrame_RGBtoYUV(frameBuffer, frameWidth, frameHeight,
|
||||
frame, width, height,
|
||||
xFlip);
|
||||
}
|
||||
else
|
||||
{
|
||||
copyFrame_YUVtoRGB(frameBuffer, frameWidth, frameHeight,
|
||||
frame, width, height,
|
||||
xFlip);
|
||||
}
|
||||
}
|
||||
|
||||
frameMutex.unlock();
|
||||
}
|
||||
|
||||
void CameraManager::feedFrame(u32* frame, int width, int height, bool yuv)
|
||||
{
|
||||
frameMutex.lock();
|
||||
|
||||
if (width == frameWidth && height == frameHeight && yuv == frameFormatYUV)
|
||||
{
|
||||
int len = width * height;
|
||||
if (yuv) len /= 2;
|
||||
memcpy(frameBuffer, frame, len * sizeof(u32));
|
||||
}
|
||||
else
|
||||
{
|
||||
if (yuv == frameFormatYUV)
|
||||
{
|
||||
copyFrame_Straight(frame, width, height,
|
||||
frameBuffer, frameWidth, frameHeight,
|
||||
false, yuv);
|
||||
}
|
||||
else if (yuv)
|
||||
{
|
||||
copyFrame_RGBtoYUV(frame, width, height,
|
||||
frameBuffer, frameWidth, frameHeight,
|
||||
false);
|
||||
}
|
||||
else
|
||||
{
|
||||
copyFrame_YUVtoRGB(frame, width, height,
|
||||
frameBuffer, frameWidth, frameHeight,
|
||||
false);
|
||||
}
|
||||
}
|
||||
|
||||
frameMutex.unlock();
|
||||
}
|
||||
|
||||
void CameraManager::feedFrame_UYVY(u32* frame, int width, int height)
|
||||
{
|
||||
for (int y = 0; y < frameHeight; y++)
|
||||
{
|
||||
int sy = (y * height) / frameHeight;
|
||||
|
||||
for (int x = 0; x < frameWidth; x+=2)
|
||||
{
|
||||
int sx = (x * width) / frameWidth;
|
||||
|
||||
u32 val = frame[((sy*width) + sx) >> 1];
|
||||
|
||||
val = ((val & 0xFF00FF00) >> 8) | ((val & 0x00FF00FF) << 8);
|
||||
|
||||
tempFrameBuffer[((y*frameWidth) + x) >> 1] = val;
|
||||
}
|
||||
}
|
||||
|
||||
feedFrame(tempFrameBuffer, frameWidth, frameHeight, true);
|
||||
}
|
||||
|
||||
void CameraManager::feedFrame_NV12(u8* planeY, u8* planeUV, int width, int height)
|
||||
{
|
||||
for (int y = 0; y < frameHeight; y++)
|
||||
{
|
||||
int sy = (y * height) / frameHeight;
|
||||
|
||||
for (int x = 0; x < frameWidth; x+=2)
|
||||
{
|
||||
int sx1 = (x * width) / frameWidth;
|
||||
int sx2 = ((x+1) * width) / frameWidth;
|
||||
|
||||
u32 val;
|
||||
|
||||
u8 y1 = planeY[(sy*width) + sx1];
|
||||
u8 y2 = planeY[(sy*width) + sx2];
|
||||
|
||||
int uvpos = (((sy>>1)*(width>>1)) + (sx1>>1));
|
||||
u8 u = planeUV[uvpos << 1];
|
||||
u8 v = planeUV[(uvpos << 1) + 1];
|
||||
|
||||
val = y1 | (u << 8) | (y2 << 16) | (v << 24);
|
||||
tempFrameBuffer[((y*frameWidth) + x) >> 1] = val;
|
||||
}
|
||||
}
|
||||
|
||||
feedFrame(tempFrameBuffer, frameWidth, frameHeight, true);
|
||||
}
|
||||
|
||||
void CameraManager::copyFrame_Straight(u32* src, int swidth, int sheight, u32* dst, int dwidth, int dheight, bool xflip, bool yuv)
|
||||
{
|
||||
if (yuv)
|
||||
{
|
||||
swidth /= 2;
|
||||
dwidth /= 2;
|
||||
}
|
||||
|
||||
for (int dy = 0; dy < dheight; dy++)
|
||||
{
|
||||
int sy = (dy * sheight) / dheight;
|
||||
|
||||
for (int dx = 0; dx < dwidth; dx++)
|
||||
{
|
||||
int sx = (dx * swidth) / dwidth;
|
||||
if (xflip) sx = swidth-1 - sx;
|
||||
|
||||
u32 val = src[(sy * swidth) + sx];
|
||||
|
||||
if (yuv)
|
||||
{
|
||||
if (xflip)
|
||||
val = (val & 0xFF00FF00) |
|
||||
((val >> 16) & 0xFF) |
|
||||
((val & 0xFF) << 16);
|
||||
}
|
||||
else
|
||||
val |= 0xFF000000;
|
||||
|
||||
dst[(dy * dwidth) + dx] = val;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CameraManager::copyFrame_RGBtoYUV(u32* src, int swidth, int sheight, u32* dst, int dwidth, int dheight, bool xflip)
|
||||
{
|
||||
for (int dy = 0; dy < dheight; dy++)
|
||||
{
|
||||
int sy = (dy * sheight) / dheight;
|
||||
|
||||
for (int dx = 0; dx < dwidth; dx+=2)
|
||||
{
|
||||
int sx;
|
||||
|
||||
sx = (dx * swidth) / dwidth;
|
||||
if (xflip) sx = swidth-1 - sx;
|
||||
|
||||
u32 pixel1 = src[sy*swidth + sx];
|
||||
|
||||
sx = ((dx+1) * swidth) / dwidth;
|
||||
if (xflip) sx = swidth-1 - sx;
|
||||
|
||||
u32 pixel2 = src[sy*swidth + sx];
|
||||
|
||||
int r1 = (pixel1 >> 16) & 0xFF;
|
||||
int g1 = (pixel1 >> 8) & 0xFF;
|
||||
int b1 = pixel1 & 0xFF;
|
||||
|
||||
int r2 = (pixel2 >> 16) & 0xFF;
|
||||
int g2 = (pixel2 >> 8) & 0xFF;
|
||||
int b2 = pixel2 & 0xFF;
|
||||
|
||||
int y1 = ((r1 * 19595) + (g1 * 38470) + (b1 * 7471)) >> 16;
|
||||
int u1 = ((b1 - y1) * 32244) >> 16;
|
||||
int v1 = ((r1 - y1) * 57475) >> 16;
|
||||
|
||||
int y2 = ((r2 * 19595) + (g2 * 38470) + (b2 * 7471)) >> 16;
|
||||
int u2 = ((b2 - y2) * 32244) >> 16;
|
||||
int v2 = ((r2 - y2) * 57475) >> 16;
|
||||
|
||||
u1 += 128; v1 += 128;
|
||||
u2 += 128; v2 += 128;
|
||||
|
||||
y1 = std::clamp(y1, 0, 255); u1 = std::clamp(u1, 0, 255); v1 = std::clamp(v1, 0, 255);
|
||||
y2 = std::clamp(y2, 0, 255); u2 = std::clamp(u2, 0, 255); v2 = std::clamp(v2, 0, 255);
|
||||
|
||||
// huh
|
||||
u1 = (u1 + u2) >> 1;
|
||||
v1 = (v1 + v2) >> 1;
|
||||
|
||||
dst[(dy*dwidth + dx) / 2] = y1 | (u1 << 8) | (y2 << 16) | (v1 << 24);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CameraManager::copyFrame_YUVtoRGB(u32* src, int swidth, int sheight, u32* dst, int dwidth, int dheight, bool xflip)
|
||||
{
|
||||
for (int dy = 0; dy < dheight; dy++)
|
||||
{
|
||||
int sy = (dy * sheight) / dheight;
|
||||
|
||||
for (int dx = 0; dx < dwidth; dx+=2)
|
||||
{
|
||||
int sx = (dx * swidth) / dwidth;
|
||||
if (xflip) sx = swidth-2 - sx;
|
||||
|
||||
u32 val = src[(sy*swidth + sx) / 2];
|
||||
|
||||
int y1, y2;
|
||||
if (xflip)
|
||||
{
|
||||
y1 = (val >> 16) & 0xFF;
|
||||
y2 = val & 0xFF;
|
||||
}
|
||||
else
|
||||
{
|
||||
y1 = val & 0xFF;
|
||||
y2 = (val >> 16) & 0xFF;
|
||||
}
|
||||
int u = (val >> 8) & 0xFF;
|
||||
int v = (val >> 24) & 0xFF;
|
||||
|
||||
u -= 128; v -= 128;
|
||||
|
||||
int r1 = y1 + ((v * 91881) >> 16);
|
||||
int g1 = y1 - ((v * 46793) >> 16) - ((u * 22544) >> 16);
|
||||
int b1 = y1 + ((u * 116129) >> 16);
|
||||
|
||||
int r2 = y2 + ((v * 91881) >> 16);
|
||||
int g2 = y2 - ((v * 46793) >> 16) - ((u * 22544) >> 16);
|
||||
int b2 = y2 + ((u * 116129) >> 16);
|
||||
|
||||
r1 = std::clamp(r1, 0, 255); g1 = std::clamp(g1, 0, 255); b1 = std::clamp(b1, 0, 255);
|
||||
r2 = std::clamp(r2, 0, 255); g2 = std::clamp(g2, 0, 255); b2 = std::clamp(b2, 0, 255);
|
||||
|
||||
u32 col1 = 0xFF000000 | (r1 << 16) | (g1 << 8) | b1;
|
||||
u32 col2 = 0xFF000000 | (r2 << 16) | (g2 << 8) | b2;
|
||||
|
||||
dst[dy*dwidth + dx ] = col1;
|
||||
dst[dy*dwidth + dx+1] = col2;
|
||||
}
|
||||
}
|
||||
}
|
134
src/frontend/qt_sdl/CameraManager.h
Normal file
134
src/frontend/qt_sdl/CameraManager.h
Normal file
@ -0,0 +1,134 @@
|
||||
/*
|
||||
Copyright 2016-2022 melonDS team
|
||||
|
||||
This file is part of melonDS.
|
||||
|
||||
melonDS 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 3 of the License, or (at your option)
|
||||
any later version.
|
||||
|
||||
melonDS 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 melonDS. If not, see http://www.gnu.org/licenses/.
|
||||
*/
|
||||
|
||||
#ifndef CAMERAMANAGER_H
|
||||
#define CAMERAMANAGER_H
|
||||
|
||||
#include <QCamera>
|
||||
#if QT_VERSION >= 0x060000
|
||||
#include <QMediaDevices>
|
||||
#include <QCameraDevice>
|
||||
#include <QMediaCaptureSession>
|
||||
#include <QVideoSink>
|
||||
#else
|
||||
#include <QCameraInfo>
|
||||
#include <QAbstractVideoSurface>
|
||||
#include <QVideoSurfaceFormat>
|
||||
#endif
|
||||
#include <QMutex>
|
||||
|
||||
#include "types.h"
|
||||
|
||||
class CameraManager;
|
||||
|
||||
|
||||
#if QT_VERSION >= 0x060000
|
||||
|
||||
class CameraFrameDumper : public QVideoSink
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
CameraFrameDumper(QObject* parent = nullptr);
|
||||
|
||||
public slots:
|
||||
void present(const QVideoFrame& frame);
|
||||
|
||||
private:
|
||||
CameraManager* cam;
|
||||
};
|
||||
|
||||
#else
|
||||
|
||||
class CameraFrameDumper : public QAbstractVideoSurface
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
CameraFrameDumper(QObject* parent = nullptr);
|
||||
|
||||
bool present(const QVideoFrame& frame) override;
|
||||
QList<QVideoFrame::PixelFormat> supportedPixelFormats(QAbstractVideoBuffer::HandleType type = QAbstractVideoBuffer::NoHandle) const override;
|
||||
|
||||
private:
|
||||
CameraManager* cam;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
class CameraManager : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
CameraManager(int num, int width, int height, bool yuv);
|
||||
~CameraManager();
|
||||
|
||||
void init();
|
||||
void deInit();
|
||||
|
||||
void start();
|
||||
void stop();
|
||||
bool isStarted();
|
||||
|
||||
void setXFlip(bool flip);
|
||||
|
||||
void captureFrame(u32* frame, int width, int height, bool yuv);
|
||||
|
||||
void feedFrame(u32* frame, int width, int height, bool yuv);
|
||||
void feedFrame_UYVY(u32* frame, int width, int height);
|
||||
void feedFrame_NV12(u8* planeY, u8* planeUV, int width, int height);
|
||||
|
||||
signals:
|
||||
void camStartSignal();
|
||||
void camStopSignal();
|
||||
|
||||
private slots:
|
||||
void camStart();
|
||||
void camStop();
|
||||
|
||||
private:
|
||||
int num;
|
||||
|
||||
int startNum;
|
||||
|
||||
int inputType;
|
||||
QString imagePath;
|
||||
QString camDeviceName;
|
||||
|
||||
QCamera* camDevice;
|
||||
CameraFrameDumper* camDumper;
|
||||
#if QT_VERSION >= 0x060000
|
||||
QMediaCaptureSession* camSession;
|
||||
#endif
|
||||
|
||||
int frameWidth, frameHeight;
|
||||
bool frameFormatYUV;
|
||||
u32* frameBuffer;
|
||||
u32* tempFrameBuffer;
|
||||
QMutex frameMutex;
|
||||
|
||||
bool xFlip;
|
||||
|
||||
void copyFrame_Straight(u32* src, int swidth, int sheight, u32* dst, int dwidth, int dheight, bool xflip, bool yuv);
|
||||
void copyFrame_RGBtoYUV(u32* src, int swidth, int sheight, u32* dst, int dwidth, int dheight, bool xflip);
|
||||
void copyFrame_YUVtoRGB(u32* src, int swidth, int sheight, u32* dst, int dwidth, int dheight, bool xflip);
|
||||
};
|
||||
|
||||
#endif // CAMERAMANAGER_H
|
304
src/frontend/qt_sdl/CameraSettingsDialog.cpp
Normal file
304
src/frontend/qt_sdl/CameraSettingsDialog.cpp
Normal file
@ -0,0 +1,304 @@
|
||||
/*
|
||||
Copyright 2016-2022 melonDS team
|
||||
|
||||
This file is part of melonDS.
|
||||
|
||||
melonDS 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 3 of the License, or (at your option)
|
||||
any later version.
|
||||
|
||||
melonDS 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 melonDS. If not, see http://www.gnu.org/licenses/.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <QFileDialog>
|
||||
#include <QPaintEvent>
|
||||
#include <QPainter>
|
||||
|
||||
#include "types.h"
|
||||
|
||||
#include "CameraSettingsDialog.h"
|
||||
#include "ui_CameraSettingsDialog.h"
|
||||
|
||||
|
||||
CameraSettingsDialog* CameraSettingsDialog::currentDlg = nullptr;
|
||||
|
||||
extern std::string EmuDirectory;
|
||||
|
||||
extern CameraManager* camManager[2];
|
||||
|
||||
|
||||
CameraPreviewPanel::CameraPreviewPanel(QWidget* parent) : QWidget(parent)
|
||||
{
|
||||
currentCam = nullptr;
|
||||
updateTimer = startTimer(50);
|
||||
}
|
||||
|
||||
CameraPreviewPanel::~CameraPreviewPanel()
|
||||
{
|
||||
killTimer(updateTimer);
|
||||
}
|
||||
|
||||
void CameraPreviewPanel::paintEvent(QPaintEvent* event)
|
||||
{
|
||||
QPainter painter(this);
|
||||
|
||||
if (!currentCam)
|
||||
{
|
||||
painter.fillRect(event->rect(), QColor::fromRgb(0, 0, 0));
|
||||
return;
|
||||
}
|
||||
|
||||
QImage picture(256, 192, QImage::Format_RGB32);
|
||||
currentCam->captureFrame((u32*)picture.bits(), 256, 192, false);
|
||||
painter.drawImage(0, 0, picture);
|
||||
}
|
||||
|
||||
|
||||
CameraSettingsDialog::CameraSettingsDialog(QWidget* parent) : QDialog(parent), ui(new Ui::CameraSettingsDialog)
|
||||
{
|
||||
previewPanel = nullptr;
|
||||
currentCfg = nullptr;
|
||||
currentCam = nullptr;
|
||||
|
||||
ui->setupUi(this);
|
||||
setAttribute(Qt::WA_DeleteOnClose);
|
||||
|
||||
for (int i = 0; i < 2; i++)
|
||||
{
|
||||
oldCamSettings[i] = Config::Camera[i];
|
||||
}
|
||||
|
||||
ui->cbCameraSel->addItem("DSi outer camera");
|
||||
ui->cbCameraSel->addItem("DSi inner camera");
|
||||
|
||||
#if QT_VERSION >= 0x060000
|
||||
const QList<QCameraDevice> cameras = QMediaDevices::videoInputs();
|
||||
for (const QCameraDevice &cameraInfo : cameras)
|
||||
{
|
||||
QString name = cameraInfo.description();
|
||||
QCameraDevice::Position pos = cameraInfo.position();
|
||||
if (pos != QCameraDevice::UnspecifiedPosition)
|
||||
{
|
||||
name += " (";
|
||||
if (pos == QCameraDevice::FrontFace)
|
||||
name += "inner camera";
|
||||
else if (pos == QCameraDevice::BackFace)
|
||||
name += "outer camera";
|
||||
name += ")";
|
||||
}
|
||||
|
||||
ui->cbPhysicalCamera->addItem(name, QString(cameraInfo.id()));
|
||||
}
|
||||
#else
|
||||
const QList<QCameraInfo> cameras = QCameraInfo::availableCameras();
|
||||
for (const QCameraInfo &cameraInfo : cameras)
|
||||
{
|
||||
QString name = cameraInfo.description();
|
||||
QCamera::Position pos = cameraInfo.position();
|
||||
if (pos != QCamera::UnspecifiedPosition)
|
||||
{
|
||||
name += " (";
|
||||
if (pos == QCamera::FrontFace)
|
||||
name += "inner camera";
|
||||
else if (pos == QCamera::BackFace)
|
||||
name += "outer camera";
|
||||
name += ")";
|
||||
}
|
||||
|
||||
ui->cbPhysicalCamera->addItem(name, cameraInfo.deviceName());
|
||||
}
|
||||
#endif
|
||||
ui->rbPictureCamera->setEnabled(ui->cbPhysicalCamera->count() > 0);
|
||||
|
||||
grpInputType = new QButtonGroup(this);
|
||||
grpInputType->addButton(ui->rbPictureNone, 0);
|
||||
grpInputType->addButton(ui->rbPictureImg, 1);
|
||||
grpInputType->addButton(ui->rbPictureCamera, 2);
|
||||
#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
|
||||
connect(grpInputType, SIGNAL(buttonClicked(int)), this, SLOT(onChangeInputType(int)));
|
||||
#else
|
||||
connect(grpInputType, SIGNAL(idClicked(int)), this, SLOT(onChangeInputType(int)));
|
||||
#endif
|
||||
|
||||
previewPanel = new CameraPreviewPanel(this);
|
||||
QVBoxLayout* previewLayout = new QVBoxLayout();
|
||||
previewLayout->addWidget(previewPanel);
|
||||
ui->grpPreview->setLayout(previewLayout);
|
||||
previewPanel->setMinimumSize(256, 192);
|
||||
previewPanel->setMaximumSize(256, 192);
|
||||
|
||||
on_cbCameraSel_currentIndexChanged(ui->cbCameraSel->currentIndex());
|
||||
}
|
||||
|
||||
CameraSettingsDialog::~CameraSettingsDialog()
|
||||
{
|
||||
delete ui;
|
||||
}
|
||||
|
||||
void CameraSettingsDialog::on_CameraSettingsDialog_accepted()
|
||||
{
|
||||
for (int i = 0; i < 2; i++)
|
||||
{
|
||||
camManager[i]->stop();
|
||||
}
|
||||
|
||||
Config::Save();
|
||||
|
||||
closeDlg();
|
||||
}
|
||||
|
||||
void CameraSettingsDialog::on_CameraSettingsDialog_rejected()
|
||||
{
|
||||
for (int i = 0; i < 2; i++)
|
||||
{
|
||||
camManager[i]->stop();
|
||||
camManager[i]->deInit();
|
||||
Config::Camera[i] = oldCamSettings[i];
|
||||
camManager[i]->init();
|
||||
}
|
||||
|
||||
closeDlg();
|
||||
}
|
||||
|
||||
void CameraSettingsDialog::on_cbCameraSel_currentIndexChanged(int id)
|
||||
{
|
||||
if (!previewPanel) return;
|
||||
|
||||
if (currentCam)
|
||||
{
|
||||
currentCam->stop();
|
||||
}
|
||||
|
||||
currentId = id;
|
||||
currentCfg = &Config::Camera[id];
|
||||
//currentCam = camManager[id];
|
||||
currentCam = nullptr;
|
||||
populateCamControls(id);
|
||||
currentCam = camManager[id];
|
||||
previewPanel->setCurrentCam(currentCam);
|
||||
|
||||
currentCam->start();
|
||||
}
|
||||
|
||||
void CameraSettingsDialog::onChangeInputType(int type)
|
||||
{
|
||||
if (!currentCfg) return;
|
||||
|
||||
if (currentCam)
|
||||
{
|
||||
currentCam->stop();
|
||||
currentCam->deInit();
|
||||
}
|
||||
|
||||
currentCfg->InputType = type;
|
||||
|
||||
ui->txtSrcImagePath->setEnabled(type == 1);
|
||||
ui->btnSrcImageBrowse->setEnabled(type == 1);
|
||||
ui->cbPhysicalCamera->setEnabled((type == 2) && (ui->cbPhysicalCamera->count()>0));
|
||||
|
||||
currentCfg->ImagePath = ui->txtSrcImagePath->text().toStdString();
|
||||
|
||||
if (ui->cbPhysicalCamera->count() > 0)
|
||||
currentCfg->CamDeviceName = ui->cbPhysicalCamera->currentData().toString().toStdString();
|
||||
|
||||
if (currentCam)
|
||||
{
|
||||
currentCam->init();
|
||||
currentCam->start();
|
||||
}
|
||||
}
|
||||
|
||||
void CameraSettingsDialog::on_txtSrcImagePath_textChanged()
|
||||
{
|
||||
if (!currentCfg) return;
|
||||
|
||||
if (currentCam)
|
||||
{
|
||||
currentCam->stop();
|
||||
currentCam->deInit();
|
||||
}
|
||||
|
||||
currentCfg->ImagePath = ui->txtSrcImagePath->text().toStdString();
|
||||
|
||||
if (currentCam)
|
||||
{
|
||||
currentCam->init();
|
||||
currentCam->start();
|
||||
}
|
||||
}
|
||||
|
||||
void CameraSettingsDialog::on_btnSrcImageBrowse_clicked()
|
||||
{
|
||||
QString file = QFileDialog::getOpenFileName(this,
|
||||
"Select image file...",
|
||||
QString::fromStdString(EmuDirectory),
|
||||
"Image files (*.png *.jpg *.jpeg *.bmp);;Any file (*.*)");
|
||||
|
||||
if (file.isEmpty()) return;
|
||||
|
||||
ui->txtSrcImagePath->setText(file);
|
||||
}
|
||||
|
||||
void CameraSettingsDialog::on_cbPhysicalCamera_currentIndexChanged(int id)
|
||||
{
|
||||
if (!currentCfg) return;
|
||||
|
||||
if (currentCam)
|
||||
{
|
||||
currentCam->stop();
|
||||
currentCam->deInit();
|
||||
}
|
||||
|
||||
currentCfg->CamDeviceName = ui->cbPhysicalCamera->itemData(id).toString().toStdString();
|
||||
|
||||
if (currentCam)
|
||||
{
|
||||
currentCam->init();
|
||||
currentCam->start();
|
||||
}
|
||||
}
|
||||
|
||||
void CameraSettingsDialog::populateCamControls(int id)
|
||||
{
|
||||
Config::CameraConfig& cfg = Config::Camera[id];
|
||||
|
||||
int type = cfg.InputType;
|
||||
if (type < 0 || type >= grpInputType->buttons().count()) type = 0;
|
||||
grpInputType->button(type)->setChecked(true);
|
||||
|
||||
ui->txtSrcImagePath->setText(QString::fromStdString(cfg.ImagePath));
|
||||
|
||||
bool deviceset = false;
|
||||
QString device = QString::fromStdString(cfg.CamDeviceName);
|
||||
for (int i = 0; i < ui->cbPhysicalCamera->count(); i++)
|
||||
{
|
||||
QString itemdev = ui->cbPhysicalCamera->itemData(i).toString();
|
||||
if (itemdev == device)
|
||||
{
|
||||
ui->cbPhysicalCamera->setCurrentIndex(i);
|
||||
deviceset = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!deviceset)
|
||||
ui->cbPhysicalCamera->setCurrentIndex(0);
|
||||
|
||||
onChangeInputType(type);
|
||||
|
||||
ui->chkFlipPicture->setChecked(cfg.XFlip);
|
||||
}
|
||||
|
||||
void CameraSettingsDialog::on_chkFlipPicture_clicked()
|
||||
{
|
||||
if (!currentCfg) return;
|
||||
|
||||
currentCfg->XFlip = ui->chkFlipPicture->isChecked();
|
||||
if (currentCam) currentCam->setXFlip(currentCfg->XFlip);
|
||||
}
|
108
src/frontend/qt_sdl/CameraSettingsDialog.h
Normal file
108
src/frontend/qt_sdl/CameraSettingsDialog.h
Normal file
@ -0,0 +1,108 @@
|
||||
/*
|
||||
Copyright 2016-2022 melonDS team
|
||||
|
||||
This file is part of melonDS.
|
||||
|
||||
melonDS 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 3 of the License, or (at your option)
|
||||
any later version.
|
||||
|
||||
melonDS 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 melonDS. If not, see http://www.gnu.org/licenses/.
|
||||
*/
|
||||
|
||||
#ifndef CAMERASETTINGSDIALOG_H
|
||||
#define CAMERASETTINGSDIALOG_H
|
||||
|
||||
#include <QDialog>
|
||||
#include <QButtonGroup>
|
||||
|
||||
#include "Config.h"
|
||||
#include "CameraManager.h"
|
||||
|
||||
namespace Ui { class CameraSettingsDialog; }
|
||||
class CameraSettingsDialog;
|
||||
|
||||
class CameraPreviewPanel : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
CameraPreviewPanel(QWidget* parent);
|
||||
~CameraPreviewPanel();
|
||||
|
||||
void setCurrentCam(CameraManager* cam)
|
||||
{
|
||||
currentCam = cam;
|
||||
}
|
||||
|
||||
protected:
|
||||
void paintEvent(QPaintEvent* event) override;
|
||||
void timerEvent(QTimerEvent* event) override
|
||||
{
|
||||
repaint();
|
||||
}
|
||||
|
||||
private:
|
||||
int updateTimer;
|
||||
CameraManager* currentCam;
|
||||
};
|
||||
|
||||
class CameraSettingsDialog : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit CameraSettingsDialog(QWidget* parent);
|
||||
~CameraSettingsDialog();
|
||||
|
||||
static CameraSettingsDialog* currentDlg;
|
||||
static CameraSettingsDialog* openDlg(QWidget* parent)
|
||||
{
|
||||
if (currentDlg)
|
||||
{
|
||||
currentDlg->activateWindow();
|
||||
return currentDlg;
|
||||
}
|
||||
|
||||
currentDlg = new CameraSettingsDialog(parent);
|
||||
currentDlg->open();
|
||||
return currentDlg;
|
||||
}
|
||||
static void closeDlg()
|
||||
{
|
||||
currentDlg = nullptr;
|
||||
}
|
||||
|
||||
private slots:
|
||||
void on_CameraSettingsDialog_accepted();
|
||||
void on_CameraSettingsDialog_rejected();
|
||||
|
||||
void on_cbCameraSel_currentIndexChanged(int id);
|
||||
void onChangeInputType(int type);
|
||||
void on_txtSrcImagePath_textChanged();
|
||||
void on_btnSrcImageBrowse_clicked();
|
||||
void on_cbPhysicalCamera_currentIndexChanged(int id);
|
||||
void on_chkFlipPicture_clicked();
|
||||
|
||||
private:
|
||||
Ui::CameraSettingsDialog* ui;
|
||||
|
||||
QButtonGroup* grpInputType;
|
||||
CameraPreviewPanel* previewPanel;
|
||||
|
||||
int currentId;
|
||||
Config::CameraConfig* currentCfg;
|
||||
CameraManager* currentCam;
|
||||
|
||||
Config::CameraConfig oldCamSettings[2];
|
||||
|
||||
void populateCamControls(int id);
|
||||
};
|
||||
|
||||
#endif // CAMERASETTINGSDIALOG_H
|
170
src/frontend/qt_sdl/CameraSettingsDialog.ui
Normal file
170
src/frontend/qt_sdl/CameraSettingsDialog.ui
Normal file
@ -0,0 +1,170 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>CameraSettingsDialog</class>
|
||||
<widget class="QDialog" name="CameraSettingsDialog">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>605</width>
|
||||
<height>341</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Camera settings - melonDS</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<property name="sizeConstraint">
|
||||
<enum>QLayout::SetFixedSize</enum>
|
||||
</property>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout" stretch="0,1">
|
||||
<item>
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Configure emulated camera:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QComboBox" name="cbCameraSel"/>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QGridLayout" name="gridLayout_3">
|
||||
<item row="0" column="0">
|
||||
<widget class="QGroupBox" name="groupBox">
|
||||
<property name="title">
|
||||
<string>Picture source</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_2">
|
||||
<item row="1" column="2">
|
||||
<widget class="QLineEdit" name="txtSrcImagePath"/>
|
||||
</item>
|
||||
<item row="0" column="0" colspan="4">
|
||||
<widget class="QRadioButton" name="rbPictureNone">
|
||||
<property name="text">
|
||||
<string>None (blank)</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="3">
|
||||
<widget class="QPushButton" name="btnSrcImageBrowse">
|
||||
<property name="text">
|
||||
<string>Browse...</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QRadioButton" name="rbPictureCamera">
|
||||
<property name="text">
|
||||
<string>Physical camera:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QRadioButton" name="rbPictureImg">
|
||||
<property name="text">
|
||||
<string>Image file:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="2" colspan="2">
|
||||
<widget class="QComboBox" name="cbPhysicalCamera"/>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QGroupBox" name="groupBox_3">
|
||||
<property name="title">
|
||||
<string>Picture settings</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_4">
|
||||
<item row="0" column="0">
|
||||
<widget class="QCheckBox" name="chkFlipPicture">
|
||||
<property name="text">
|
||||
<string>Flip horizontally</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<spacer name="verticalSpacer">
|
||||
<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 row="0" column="2" rowspan="3">
|
||||
<widget class="QGroupBox" name="grpPreview">
|
||||
<property name="title">
|
||||
<string>Preview</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QDialogButtonBox" name="buttonBox">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="standardButtons">
|
||||
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>accepted()</signal>
|
||||
<receiver>CameraSettingsDialog</receiver>
|
||||
<slot>accept()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>248</x>
|
||||
<y>254</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>157</x>
|
||||
<y>274</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>rejected()</signal>
|
||||
<receiver>CameraSettingsDialog</receiver>
|
||||
<slot>reject()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>316</x>
|
||||
<y>260</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>286</x>
|
||||
<y>274</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
</connections>
|
||||
</ui>
|
@ -106,9 +106,10 @@ int FirmwareBirthdayDay;
|
||||
int FirmwareFavouriteColour;
|
||||
std::string FirmwareMessage;
|
||||
std::string FirmwareMAC;
|
||||
bool RandomizeMAC;
|
||||
|
||||
bool SocketBindAnyAddr;
|
||||
int MPAudioMode;
|
||||
int MPRecvTimeout;
|
||||
|
||||
std::string LANDevice;
|
||||
bool DirectLAN;
|
||||
|
||||
@ -139,202 +140,211 @@ bool DSBatteryLevelOkay;
|
||||
int DSiBatteryLevel;
|
||||
bool DSiBatteryCharging;
|
||||
|
||||
CameraConfig Camera[2];
|
||||
|
||||
|
||||
const char* kConfigFile = "melonDS.ini";
|
||||
const char* kUniqueConfigFile = "melonDS.%d.ini";
|
||||
|
||||
ConfigEntry ConfigFile[] =
|
||||
{
|
||||
{"Key_A", 0, &KeyMapping[0], -1},
|
||||
{"Key_B", 0, &KeyMapping[1], -1},
|
||||
{"Key_Select", 0, &KeyMapping[2], -1},
|
||||
{"Key_Start", 0, &KeyMapping[3], -1},
|
||||
{"Key_Right", 0, &KeyMapping[4], -1},
|
||||
{"Key_Left", 0, &KeyMapping[5], -1},
|
||||
{"Key_Up", 0, &KeyMapping[6], -1},
|
||||
{"Key_Down", 0, &KeyMapping[7], -1},
|
||||
{"Key_R", 0, &KeyMapping[8], -1},
|
||||
{"Key_L", 0, &KeyMapping[9], -1},
|
||||
{"Key_X", 0, &KeyMapping[10], -1},
|
||||
{"Key_Y", 0, &KeyMapping[11], -1},
|
||||
{"Key_A", 0, &KeyMapping[0], -1, true},
|
||||
{"Key_B", 0, &KeyMapping[1], -1, true},
|
||||
{"Key_Select", 0, &KeyMapping[2], -1, true},
|
||||
{"Key_Start", 0, &KeyMapping[3], -1, true},
|
||||
{"Key_Right", 0, &KeyMapping[4], -1, true},
|
||||
{"Key_Left", 0, &KeyMapping[5], -1, true},
|
||||
{"Key_Up", 0, &KeyMapping[6], -1, true},
|
||||
{"Key_Down", 0, &KeyMapping[7], -1, true},
|
||||
{"Key_R", 0, &KeyMapping[8], -1, true},
|
||||
{"Key_L", 0, &KeyMapping[9], -1, true},
|
||||
{"Key_X", 0, &KeyMapping[10], -1, true},
|
||||
{"Key_Y", 0, &KeyMapping[11], -1, true},
|
||||
|
||||
{"Joy_A", 0, &JoyMapping[0], -1},
|
||||
{"Joy_B", 0, &JoyMapping[1], -1},
|
||||
{"Joy_Select", 0, &JoyMapping[2], -1},
|
||||
{"Joy_Start", 0, &JoyMapping[3], -1},
|
||||
{"Joy_Right", 0, &JoyMapping[4], -1},
|
||||
{"Joy_Left", 0, &JoyMapping[5], -1},
|
||||
{"Joy_Up", 0, &JoyMapping[6], -1},
|
||||
{"Joy_Down", 0, &JoyMapping[7], -1},
|
||||
{"Joy_R", 0, &JoyMapping[8], -1},
|
||||
{"Joy_L", 0, &JoyMapping[9], -1},
|
||||
{"Joy_X", 0, &JoyMapping[10], -1},
|
||||
{"Joy_Y", 0, &JoyMapping[11], -1},
|
||||
{"Joy_A", 0, &JoyMapping[0], -1, true},
|
||||
{"Joy_B", 0, &JoyMapping[1], -1, true},
|
||||
{"Joy_Select", 0, &JoyMapping[2], -1, true},
|
||||
{"Joy_Start", 0, &JoyMapping[3], -1, true},
|
||||
{"Joy_Right", 0, &JoyMapping[4], -1, true},
|
||||
{"Joy_Left", 0, &JoyMapping[5], -1, true},
|
||||
{"Joy_Up", 0, &JoyMapping[6], -1, true},
|
||||
{"Joy_Down", 0, &JoyMapping[7], -1, true},
|
||||
{"Joy_R", 0, &JoyMapping[8], -1, true},
|
||||
{"Joy_L", 0, &JoyMapping[9], -1, true},
|
||||
{"Joy_X", 0, &JoyMapping[10], -1, true},
|
||||
{"Joy_Y", 0, &JoyMapping[11], -1, true},
|
||||
|
||||
{"HKKey_Lid", 0, &HKKeyMapping[HK_Lid], -1},
|
||||
{"HKKey_Mic", 0, &HKKeyMapping[HK_Mic], -1},
|
||||
{"HKKey_Pause", 0, &HKKeyMapping[HK_Pause], -1},
|
||||
{"HKKey_Reset", 0, &HKKeyMapping[HK_Reset], -1},
|
||||
{"HKKey_FastForward", 0, &HKKeyMapping[HK_FastForward], -1},
|
||||
{"HKKey_FastForwardToggle", 0, &HKKeyMapping[HK_FastForwardToggle], -1},
|
||||
{"HKKey_FullscreenToggle", 0, &HKKeyMapping[HK_FullscreenToggle], -1},
|
||||
{"HKKey_SwapScreens", 0, &HKKeyMapping[HK_SwapScreens], -1},
|
||||
{"HKKey_SolarSensorDecrease", 0, &HKKeyMapping[HK_SolarSensorDecrease], -1},
|
||||
{"HKKey_SolarSensorIncrease", 0, &HKKeyMapping[HK_SolarSensorIncrease], -1},
|
||||
{"HKKey_FrameStep", 0, &HKKeyMapping[HK_FrameStep], -1},
|
||||
{"HKKey_Lid", 0, &HKKeyMapping[HK_Lid], -1, true},
|
||||
{"HKKey_Mic", 0, &HKKeyMapping[HK_Mic], -1, true},
|
||||
{"HKKey_Pause", 0, &HKKeyMapping[HK_Pause], -1, true},
|
||||
{"HKKey_Reset", 0, &HKKeyMapping[HK_Reset], -1, true},
|
||||
{"HKKey_FastForward", 0, &HKKeyMapping[HK_FastForward], -1, true},
|
||||
{"HKKey_FastForwardToggle", 0, &HKKeyMapping[HK_FastForwardToggle], -1, true},
|
||||
{"HKKey_FullscreenToggle", 0, &HKKeyMapping[HK_FullscreenToggle], -1, true},
|
||||
{"HKKey_SwapScreens", 0, &HKKeyMapping[HK_SwapScreens], -1, true},
|
||||
{"HKKey_SolarSensorDecrease", 0, &HKKeyMapping[HK_SolarSensorDecrease], -1, true},
|
||||
{"HKKey_SolarSensorIncrease", 0, &HKKeyMapping[HK_SolarSensorIncrease], -1, true},
|
||||
{"HKKey_FrameStep", 0, &HKKeyMapping[HK_FrameStep], -1, true},
|
||||
|
||||
{"HKJoy_Lid", 0, &HKJoyMapping[HK_Lid], -1},
|
||||
{"HKJoy_Mic", 0, &HKJoyMapping[HK_Mic], -1},
|
||||
{"HKJoy_Pause", 0, &HKJoyMapping[HK_Pause], -1},
|
||||
{"HKJoy_Reset", 0, &HKJoyMapping[HK_Reset], -1},
|
||||
{"HKJoy_FastForward", 0, &HKJoyMapping[HK_FastForward], -1},
|
||||
{"HKJoy_FastForwardToggle", 0, &HKJoyMapping[HK_FastForwardToggle], -1},
|
||||
{"HKJoy_FullscreenToggle", 0, &HKJoyMapping[HK_FullscreenToggle], -1},
|
||||
{"HKJoy_SwapScreens", 0, &HKJoyMapping[HK_SwapScreens], -1},
|
||||
{"HKJoy_SolarSensorDecrease", 0, &HKJoyMapping[HK_SolarSensorDecrease], -1},
|
||||
{"HKJoy_SolarSensorIncrease", 0, &HKJoyMapping[HK_SolarSensorIncrease], -1},
|
||||
{"HKJoy_FrameStep", 0, &HKJoyMapping[HK_FrameStep], -1},
|
||||
{"HKJoy_Lid", 0, &HKJoyMapping[HK_Lid], -1, true},
|
||||
{"HKJoy_Mic", 0, &HKJoyMapping[HK_Mic], -1, true},
|
||||
{"HKJoy_Pause", 0, &HKJoyMapping[HK_Pause], -1, true},
|
||||
{"HKJoy_Reset", 0, &HKJoyMapping[HK_Reset], -1, true},
|
||||
{"HKJoy_FastForward", 0, &HKJoyMapping[HK_FastForward], -1, true},
|
||||
{"HKJoy_FastForwardToggle", 0, &HKJoyMapping[HK_FastForwardToggle], -1, true},
|
||||
{"HKJoy_FullscreenToggle", 0, &HKJoyMapping[HK_FullscreenToggle], -1, true},
|
||||
{"HKJoy_SwapScreens", 0, &HKJoyMapping[HK_SwapScreens], -1, true},
|
||||
{"HKJoy_SolarSensorDecrease", 0, &HKJoyMapping[HK_SolarSensorDecrease], -1, true},
|
||||
{"HKJoy_SolarSensorIncrease", 0, &HKJoyMapping[HK_SolarSensorIncrease], -1, true},
|
||||
{"HKJoy_FrameStep", 0, &HKJoyMapping[HK_FrameStep], -1, true},
|
||||
|
||||
{"JoystickID", 0, &JoystickID, 0},
|
||||
{"JoystickID", 0, &JoystickID, 0, true},
|
||||
|
||||
{"WindowWidth", 0, &WindowWidth, 256},
|
||||
{"WindowHeight", 0, &WindowHeight, 384},
|
||||
{"WindowMax", 1, &WindowMaximized, false},
|
||||
{"WindowWidth", 0, &WindowWidth, 256, true},
|
||||
{"WindowHeight", 0, &WindowHeight, 384, true},
|
||||
{"WindowMax", 1, &WindowMaximized, false, true},
|
||||
|
||||
{"ScreenRotation", 0, &ScreenRotation, 0},
|
||||
{"ScreenGap", 0, &ScreenGap, 0},
|
||||
{"ScreenLayout", 0, &ScreenLayout, 0},
|
||||
{"ScreenSwap", 1, &ScreenSwap, false},
|
||||
{"ScreenSizing", 0, &ScreenSizing, 0},
|
||||
{"IntegerScaling", 1, &IntegerScaling, false},
|
||||
{"ScreenAspectTop",0, &ScreenAspectTop,0},
|
||||
{"ScreenAspectBot",0, &ScreenAspectBot,0},
|
||||
{"ScreenFilter", 1, &ScreenFilter, true},
|
||||
{"ScreenRotation", 0, &ScreenRotation, 0, true},
|
||||
{"ScreenGap", 0, &ScreenGap, 0, true},
|
||||
{"ScreenLayout", 0, &ScreenLayout, 0, true},
|
||||
{"ScreenSwap", 1, &ScreenSwap, false, true},
|
||||
{"ScreenSizing", 0, &ScreenSizing, 0, true},
|
||||
{"IntegerScaling", 1, &IntegerScaling, false, true},
|
||||
{"ScreenAspectTop",0, &ScreenAspectTop,0, true},
|
||||
{"ScreenAspectBot",0, &ScreenAspectBot,0, true},
|
||||
{"ScreenFilter", 1, &ScreenFilter, true, true},
|
||||
|
||||
{"ScreenUseGL", 1, &ScreenUseGL, false},
|
||||
{"ScreenVSync", 1, &ScreenVSync, false},
|
||||
{"ScreenVSyncInterval", 0, &ScreenVSyncInterval, 1},
|
||||
{"ScreenUseGL", 1, &ScreenUseGL, false, false},
|
||||
{"ScreenVSync", 1, &ScreenVSync, false, false},
|
||||
{"ScreenVSyncInterval", 0, &ScreenVSyncInterval, 1, false},
|
||||
|
||||
{"3DRenderer", 0, &_3DRenderer, 0},
|
||||
{"Threaded3D", 1, &Threaded3D, true},
|
||||
{"3DRenderer", 0, &_3DRenderer, 0, false},
|
||||
{"Threaded3D", 1, &Threaded3D, true, false},
|
||||
|
||||
{"GL_ScaleFactor", 0, &GL_ScaleFactor, 1},
|
||||
{"GL_BetterPolygons", 1, &GL_BetterPolygons, false},
|
||||
{"GL_ScaleFactor", 0, &GL_ScaleFactor, 1, false},
|
||||
{"GL_BetterPolygons", 1, &GL_BetterPolygons, false, false},
|
||||
|
||||
{"LimitFPS", 1, &LimitFPS, true},
|
||||
{"LimitFPS", 1, &LimitFPS, true, false},
|
||||
{"AudioSync", 1, &AudioSync, false},
|
||||
{"ShowOSD", 1, &ShowOSD, true},
|
||||
{"ShowOSD", 1, &ShowOSD, true, false},
|
||||
|
||||
{"ConsoleType", 0, &ConsoleType, 0},
|
||||
{"DirectBoot", 1, &DirectBoot, true},
|
||||
{"ConsoleType", 0, &ConsoleType, 0, false},
|
||||
{"DirectBoot", 1, &DirectBoot, true, false},
|
||||
|
||||
#ifdef JIT_ENABLED
|
||||
{"JIT_Enable", 1, &JIT_Enable, false},
|
||||
{"JIT_MaxBlockSize", 0, &JIT_MaxBlockSize, 32},
|
||||
{"JIT_BranchOptimisations", 1, &JIT_BranchOptimisations, true},
|
||||
{"JIT_LiteralOptimisations", 1, &JIT_LiteralOptimisations, true},
|
||||
{"JIT_Enable", 1, &JIT_Enable, false, false},
|
||||
{"JIT_MaxBlockSize", 0, &JIT_MaxBlockSize, 32, false},
|
||||
{"JIT_BranchOptimisations", 1, &JIT_BranchOptimisations, true, false},
|
||||
{"JIT_LiteralOptimisations", 1, &JIT_LiteralOptimisations, true, false},
|
||||
#ifdef __APPLE__
|
||||
{"JIT_FastMemory", 1, &JIT_FastMemory, false},
|
||||
{"JIT_FastMemory", 1, &JIT_FastMemory, false, false},
|
||||
#else
|
||||
{"JIT_FastMemory", 1, &JIT_FastMemory, true},
|
||||
{"JIT_FastMemory", 1, &JIT_FastMemory, true, false},
|
||||
#endif
|
||||
#endif
|
||||
|
||||
{"ExternalBIOSEnable", 1, &ExternalBIOSEnable, false},
|
||||
{"ExternalBIOSEnable", 1, &ExternalBIOSEnable, false, false},
|
||||
|
||||
{"BIOS9Path", 2, &BIOS9Path, (std::string)""},
|
||||
{"BIOS7Path", 2, &BIOS7Path, (std::string)""},
|
||||
{"FirmwarePath", 2, &FirmwarePath, (std::string)""},
|
||||
{"BIOS9Path", 2, &BIOS9Path, (std::string)"", false},
|
||||
{"BIOS7Path", 2, &BIOS7Path, (std::string)"", false},
|
||||
{"FirmwarePath", 2, &FirmwarePath, (std::string)"", false},
|
||||
|
||||
{"DSiBIOS9Path", 2, &DSiBIOS9Path, (std::string)""},
|
||||
{"DSiBIOS7Path", 2, &DSiBIOS7Path, (std::string)""},
|
||||
{"DSiFirmwarePath", 2, &DSiFirmwarePath, (std::string)""},
|
||||
{"DSiNANDPath", 2, &DSiNANDPath, (std::string)""},
|
||||
{"DSiBIOS9Path", 2, &DSiBIOS9Path, (std::string)"", false},
|
||||
{"DSiBIOS7Path", 2, &DSiBIOS7Path, (std::string)"", false},
|
||||
{"DSiFirmwarePath", 2, &DSiFirmwarePath, (std::string)"", false},
|
||||
{"DSiNANDPath", 2, &DSiNANDPath, (std::string)"", false},
|
||||
|
||||
{"DLDIEnable", 1, &DLDIEnable, false},
|
||||
{"DLDISDPath", 2, &DLDISDPath, (std::string)"dldi.bin"},
|
||||
{"DLDISize", 0, &DLDISize, 0},
|
||||
{"DLDIReadOnly", 1, &DLDIReadOnly, false},
|
||||
{"DLDIFolderSync", 1, &DLDIFolderSync, false},
|
||||
{"DLDIFolderPath", 2, &DLDIFolderPath, (std::string)""},
|
||||
{"DLDIEnable", 1, &DLDIEnable, false, false},
|
||||
{"DLDISDPath", 2, &DLDISDPath, (std::string)"dldi.bin", false},
|
||||
{"DLDISize", 0, &DLDISize, 0, false},
|
||||
{"DLDIReadOnly", 1, &DLDIReadOnly, false, false},
|
||||
{"DLDIFolderSync", 1, &DLDIFolderSync, false, false},
|
||||
{"DLDIFolderPath", 2, &DLDIFolderPath, (std::string)"", false},
|
||||
|
||||
{"DSiSDEnable", 1, &DSiSDEnable, false},
|
||||
{"DSiSDPath", 2, &DSiSDPath, (std::string)"dsisd.bin"},
|
||||
{"DSiSDSize", 0, &DSiSDSize, 0},
|
||||
{"DSiSDReadOnly", 1, &DSiSDReadOnly, false},
|
||||
{"DSiSDFolderSync", 1, &DSiSDFolderSync, false},
|
||||
{"DSiSDFolderPath", 2, &DSiSDFolderPath, (std::string)""},
|
||||
{"DSiSDEnable", 1, &DSiSDEnable, false, false},
|
||||
{"DSiSDPath", 2, &DSiSDPath, (std::string)"dsisd.bin", false},
|
||||
{"DSiSDSize", 0, &DSiSDSize, 0, false},
|
||||
{"DSiSDReadOnly", 1, &DSiSDReadOnly, false, false},
|
||||
{"DSiSDFolderSync", 1, &DSiSDFolderSync, false, false},
|
||||
{"DSiSDFolderPath", 2, &DSiSDFolderPath, (std::string)"", false},
|
||||
|
||||
{"FirmwareOverrideSettings", 1, &FirmwareOverrideSettings, false},
|
||||
{"FirmwareUsername", 2, &FirmwareUsername, (std::string)"melonDS"},
|
||||
{"FirmwareLanguage", 0, &FirmwareLanguage, 1},
|
||||
{"FirmwareBirthdayMonth", 0, &FirmwareBirthdayMonth, 1},
|
||||
{"FirmwareBirthdayDay", 0, &FirmwareBirthdayDay, 1},
|
||||
{"FirmwareFavouriteColour", 0, &FirmwareFavouriteColour, 0},
|
||||
{"FirmwareMessage", 2, &FirmwareMessage, (std::string)""},
|
||||
{"FirmwareMAC", 2, &FirmwareMAC, (std::string)""},
|
||||
{"RandomizeMAC", 1, &RandomizeMAC, false},
|
||||
{"FirmwareOverrideSettings", 1, &FirmwareOverrideSettings, false, true},
|
||||
{"FirmwareUsername", 2, &FirmwareUsername, (std::string)"melonDS", true},
|
||||
{"FirmwareLanguage", 0, &FirmwareLanguage, 1, true},
|
||||
{"FirmwareBirthdayMonth", 0, &FirmwareBirthdayMonth, 1, true},
|
||||
{"FirmwareBirthdayDay", 0, &FirmwareBirthdayDay, 1, true},
|
||||
{"FirmwareFavouriteColour", 0, &FirmwareFavouriteColour, 0, true},
|
||||
{"FirmwareMessage", 2, &FirmwareMessage, (std::string)"", true},
|
||||
{"FirmwareMAC", 2, &FirmwareMAC, (std::string)"", true},
|
||||
|
||||
{"SockBindAnyAddr", 1, &SocketBindAnyAddr, false},
|
||||
{"LANDevice", 2, &LANDevice, (std::string)""},
|
||||
{"DirectLAN", 1, &DirectLAN, false},
|
||||
{"MPAudioMode", 0, &MPAudioMode, 1, false},
|
||||
{"MPRecvTimeout", 0, &MPRecvTimeout, 25, false},
|
||||
|
||||
{"SavStaRelocSRAM", 1, &SavestateRelocSRAM, false},
|
||||
{"LANDevice", 2, &LANDevice, (std::string)"", false},
|
||||
{"DirectLAN", 1, &DirectLAN, false, false},
|
||||
|
||||
{"AudioInterp", 0, &AudioInterp, 0},
|
||||
{"AudioBitrate", 0, &AudioBitrate, 0},
|
||||
{"AudioVolume", 0, &AudioVolume, 256},
|
||||
{"MicInputType", 0, &MicInputType, 1},
|
||||
{"MicWavPath", 2, &MicWavPath, (std::string)""},
|
||||
{"SavStaRelocSRAM", 1, &SavestateRelocSRAM, false, false},
|
||||
|
||||
{"LastROMFolder", 2, &LastROMFolder, (std::string)""},
|
||||
{"AudioInterp", 0, &AudioInterp, 0, false},
|
||||
{"AudioBitrate", 0, &AudioBitrate, 0, false},
|
||||
{"AudioVolume", 0, &AudioVolume, 256, true},
|
||||
{"MicInputType", 0, &MicInputType, 1, false},
|
||||
{"MicWavPath", 2, &MicWavPath, (std::string)"", false},
|
||||
|
||||
{"RecentROM_0", 2, &RecentROMList[0], (std::string)""},
|
||||
{"RecentROM_1", 2, &RecentROMList[1], (std::string)""},
|
||||
{"RecentROM_2", 2, &RecentROMList[2], (std::string)""},
|
||||
{"RecentROM_3", 2, &RecentROMList[3], (std::string)""},
|
||||
{"RecentROM_4", 2, &RecentROMList[4], (std::string)""},
|
||||
{"RecentROM_5", 2, &RecentROMList[5], (std::string)""},
|
||||
{"RecentROM_6", 2, &RecentROMList[6], (std::string)""},
|
||||
{"RecentROM_7", 2, &RecentROMList[7], (std::string)""},
|
||||
{"RecentROM_8", 2, &RecentROMList[8], (std::string)""},
|
||||
{"RecentROM_9", 2, &RecentROMList[9], (std::string)""},
|
||||
{"LastROMFolder", 2, &LastROMFolder, (std::string)"", true},
|
||||
|
||||
{"SaveFilePath", 2, &SaveFilePath, (std::string)""},
|
||||
{"SavestatePath", 2, &SavestatePath, (std::string)""},
|
||||
{"CheatFilePath", 2, &CheatFilePath, (std::string)""},
|
||||
{"RecentROM_0", 2, &RecentROMList[0], (std::string)"", true},
|
||||
{"RecentROM_1", 2, &RecentROMList[1], (std::string)"", true},
|
||||
{"RecentROM_2", 2, &RecentROMList[2], (std::string)"", true},
|
||||
{"RecentROM_3", 2, &RecentROMList[3], (std::string)"", true},
|
||||
{"RecentROM_4", 2, &RecentROMList[4], (std::string)"", true},
|
||||
{"RecentROM_5", 2, &RecentROMList[5], (std::string)"", true},
|
||||
{"RecentROM_6", 2, &RecentROMList[6], (std::string)"", true},
|
||||
{"RecentROM_7", 2, &RecentROMList[7], (std::string)"", true},
|
||||
{"RecentROM_8", 2, &RecentROMList[8], (std::string)"", true},
|
||||
{"RecentROM_9", 2, &RecentROMList[9], (std::string)"", true},
|
||||
|
||||
{"EnableCheats", 1, &EnableCheats, false},
|
||||
{"SaveFilePath", 2, &SaveFilePath, (std::string)"", true},
|
||||
{"SavestatePath", 2, &SavestatePath, (std::string)"", true},
|
||||
{"CheatFilePath", 2, &CheatFilePath, (std::string)"", true},
|
||||
|
||||
{"MouseHide", 1, &MouseHide, false},
|
||||
{"MouseHideSeconds", 0, &MouseHideSeconds, 5},
|
||||
{"PauseLostFocus", 1, &PauseLostFocus, false},
|
||||
{"EnableCheats", 1, &EnableCheats, false, true},
|
||||
|
||||
{"DSBatteryLevelOkay", 1, &DSBatteryLevelOkay, true},
|
||||
{"DSiBatteryLevel", 0, &DSiBatteryLevel, 0xF},
|
||||
{"DSiBatteryCharging", 1, &DSiBatteryCharging, true},
|
||||
{"MouseHide", 1, &MouseHide, false, false},
|
||||
{"MouseHideSeconds", 0, &MouseHideSeconds, 5, false},
|
||||
{"PauseLostFocus", 1, &PauseLostFocus, false, false},
|
||||
|
||||
{"", -1, nullptr, 0}
|
||||
{"DSBatteryLevelOkay", 1, &DSBatteryLevelOkay, true, true},
|
||||
{"DSiBatteryLevel", 0, &DSiBatteryLevel, 0xF, true},
|
||||
{"DSiBatteryCharging", 1, &DSiBatteryCharging, true, true},
|
||||
|
||||
// TODO!!
|
||||
// we need a more elegant way to deal with this
|
||||
{"Camera0_InputType", 0, &Camera[0].InputType, 0, false},
|
||||
{"Camera0_ImagePath", 2, &Camera[0].ImagePath, (std::string)"", false},
|
||||
{"Camera0_CamDeviceName", 2, &Camera[0].CamDeviceName, (std::string)"", false},
|
||||
{"Camera0_XFlip", 1, &Camera[0].XFlip, false, false},
|
||||
{"Camera1_InputType", 0, &Camera[1].InputType, 0, false},
|
||||
{"Camera1_ImagePath", 2, &Camera[1].ImagePath, (std::string)"", false},
|
||||
{"Camera1_CamDeviceName", 2, &Camera[1].CamDeviceName, (std::string)"", false},
|
||||
{"Camera1_XFlip", 1, &Camera[1].XFlip, false, false},
|
||||
|
||||
{"", -1, nullptr, 0, false}
|
||||
};
|
||||
|
||||
|
||||
void Load()
|
||||
void LoadFile(int inst)
|
||||
{
|
||||
ConfigEntry* entry = &ConfigFile[0];
|
||||
for (;;)
|
||||
FILE* f;
|
||||
if (inst > 0)
|
||||
{
|
||||
if (!entry->Value) break;
|
||||
|
||||
switch (entry->Type)
|
||||
{
|
||||
case 0: *(int*)entry->Value = std::get<int>(entry->Default); break;
|
||||
case 1: *(bool*)entry->Value = std::get<bool>(entry->Default); break;
|
||||
case 2: *(std::string*)entry->Value = std::get<std::string>(entry->Default); break;
|
||||
}
|
||||
|
||||
entry++;
|
||||
char name[100] = {0};
|
||||
snprintf(name, 99, kUniqueConfigFile, inst+1);
|
||||
f = Platform::OpenLocalFile(name, "r");
|
||||
}
|
||||
else
|
||||
f = Platform::OpenLocalFile(kConfigFile, "r");
|
||||
|
||||
FILE* f = Platform::OpenLocalFile(kConfigFile, "r");
|
||||
if (!f) return;
|
||||
|
||||
char linebuf[1024];
|
||||
@ -349,13 +359,13 @@ void Load()
|
||||
entryname[31] = '\0';
|
||||
if (ret < 2) continue;
|
||||
|
||||
ConfigEntry* entry = &ConfigFile[0];
|
||||
for (;;)
|
||||
for (ConfigEntry* entry = &ConfigFile[0]; entry->Value; entry++)
|
||||
{
|
||||
if (!entry->Value) break;
|
||||
|
||||
if (!strncmp(entry->Name, entryname, 32))
|
||||
{
|
||||
if ((inst > 0) && (!entry->InstanceUnique))
|
||||
break;
|
||||
|
||||
switch (entry->Type)
|
||||
{
|
||||
case 0: *(int*)entry->Value = strtol(entryval, NULL, 10); break;
|
||||
@ -365,23 +375,52 @@ void Load()
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
entry++;
|
||||
}
|
||||
}
|
||||
|
||||
fclose(f);
|
||||
}
|
||||
|
||||
void Load()
|
||||
{
|
||||
|
||||
for (ConfigEntry* entry = &ConfigFile[0]; entry->Value; entry++)
|
||||
{
|
||||
switch (entry->Type)
|
||||
{
|
||||
case 0: *(int*)entry->Value = std::get<int>(entry->Default); break;
|
||||
case 1: *(bool*)entry->Value = std::get<bool>(entry->Default); break;
|
||||
case 2: *(std::string*)entry->Value = std::get<std::string>(entry->Default); break;
|
||||
}
|
||||
}
|
||||
|
||||
LoadFile(0);
|
||||
|
||||
int inst = Platform::InstanceID();
|
||||
if (inst > 0)
|
||||
LoadFile(inst);
|
||||
}
|
||||
|
||||
void Save()
|
||||
{
|
||||
FILE* f = Platform::OpenLocalFile(kConfigFile, "w");
|
||||
int inst = Platform::InstanceID();
|
||||
|
||||
FILE* f;
|
||||
if (inst > 0)
|
||||
{
|
||||
char name[100] = {0};
|
||||
snprintf(name, 99, kUniqueConfigFile, inst+1);
|
||||
f = Platform::OpenLocalFile(name, "w");
|
||||
}
|
||||
else
|
||||
f = Platform::OpenLocalFile(kConfigFile, "w");
|
||||
|
||||
if (!f) return;
|
||||
|
||||
ConfigEntry* entry = &ConfigFile[0];
|
||||
for (;;)
|
||||
for (ConfigEntry* entry = &ConfigFile[0]; entry->Value; entry++)
|
||||
{
|
||||
if (!entry->Value) break;
|
||||
if ((inst > 0) && (!entry->InstanceUnique))
|
||||
continue;
|
||||
|
||||
switch (entry->Type)
|
||||
{
|
||||
@ -389,8 +428,6 @@ void Save()
|
||||
case 1: fprintf(f, "%s=%d\r\n", entry->Name, *(bool*)entry->Value ? 1:0); break;
|
||||
case 2: fprintf(f, "%s=%s\r\n", entry->Name, (*(std::string*)entry->Value).c_str()); break;
|
||||
}
|
||||
|
||||
entry++;
|
||||
}
|
||||
|
||||
fclose(f);
|
||||
|
@ -58,6 +58,15 @@ struct ConfigEntry
|
||||
int Type; // 0=int 1=bool 2=string
|
||||
void* Value; // pointer to the value variable
|
||||
std::variant<int, bool, std::string> Default;
|
||||
bool InstanceUnique; // whether the setting can exist individually for each instance in multiplayer
|
||||
};
|
||||
|
||||
struct CameraConfig
|
||||
{
|
||||
int InputType; // 0=blank 1=image 2=camera
|
||||
std::string ImagePath;
|
||||
std::string CamDeviceName;
|
||||
bool XFlip;
|
||||
};
|
||||
|
||||
|
||||
@ -141,9 +150,10 @@ extern int FirmwareBirthdayDay;
|
||||
extern int FirmwareFavouriteColour;
|
||||
extern std::string FirmwareMessage;
|
||||
extern std::string FirmwareMAC;
|
||||
extern bool RandomizeMAC;
|
||||
|
||||
extern bool SocketBindAnyAddr;
|
||||
extern int MPAudioMode;
|
||||
extern int MPRecvTimeout;
|
||||
|
||||
extern std::string LANDevice;
|
||||
extern bool DirectLAN;
|
||||
|
||||
@ -173,6 +183,8 @@ extern bool DSBatteryLevelOkay;
|
||||
extern int DSiBatteryLevel;
|
||||
extern bool DSiBatteryCharging;
|
||||
|
||||
extern CameraConfig Camera[2];
|
||||
|
||||
|
||||
void Load();
|
||||
void Save();
|
||||
|
@ -18,6 +18,7 @@
|
||||
|
||||
#include <QMessageBox>
|
||||
|
||||
#include "Platform.h"
|
||||
#include "Config.h"
|
||||
|
||||
#include "FirmwareSettingsDialog.h"
|
||||
@ -64,10 +65,14 @@ FirmwareSettingsDialog::FirmwareSettingsDialog(QWidget* parent) : QDialog(parent
|
||||
ui->overrideFirmwareBox->setChecked(Config::FirmwareOverrideSettings);
|
||||
|
||||
ui->txtMAC->setText(QString::fromStdString(Config::FirmwareMAC));
|
||||
ui->cbRandomizeMAC->setChecked(Config::RandomizeMAC);
|
||||
|
||||
on_overrideFirmwareBox_toggled();
|
||||
on_cbRandomizeMAC_toggled();
|
||||
|
||||
int inst = Platform::InstanceID();
|
||||
if (inst > 0)
|
||||
ui->lblInstanceNum->setText(QString("Configuring settings for instance %1").arg(inst+1));
|
||||
else
|
||||
ui->lblInstanceNum->hide();
|
||||
}
|
||||
|
||||
FirmwareSettingsDialog::~FirmwareSettingsDialog()
|
||||
@ -135,7 +140,6 @@ void FirmwareSettingsDialog::done(int r)
|
||||
std::string newMessage = ui->messageEdit->text().toStdString();
|
||||
|
||||
std::string newMAC = ui->txtMAC->text().toStdString();
|
||||
bool newRandomizeMAC = ui->cbRandomizeMAC->isChecked();
|
||||
|
||||
if ( newOverride != Config::FirmwareOverrideSettings
|
||||
|| newName != Config::FirmwareUsername
|
||||
@ -144,8 +148,7 @@ void FirmwareSettingsDialog::done(int r)
|
||||
|| newBirthdayDay != Config::FirmwareBirthdayDay
|
||||
|| newBirthdayMonth != Config::FirmwareBirthdayMonth
|
||||
|| newMessage != Config::FirmwareMessage
|
||||
|| newMAC != Config::FirmwareMAC
|
||||
|| newRandomizeMAC != Config::RandomizeMAC)
|
||||
|| newMAC != Config::FirmwareMAC)
|
||||
{
|
||||
if (RunningSomething
|
||||
&& QMessageBox::warning(this, "Reset necessary to apply changes",
|
||||
@ -163,7 +166,6 @@ void FirmwareSettingsDialog::done(int r)
|
||||
Config::FirmwareMessage = newMessage;
|
||||
|
||||
Config::FirmwareMAC = newMAC;
|
||||
Config::RandomizeMAC = newRandomizeMAC;
|
||||
|
||||
Config::Save();
|
||||
|
||||
@ -210,9 +212,3 @@ void FirmwareSettingsDialog::on_overrideFirmwareBox_toggled()
|
||||
ui->grpUserSettings->setDisabled(disable);
|
||||
ui->grpWifiSettings->setDisabled(disable);
|
||||
}
|
||||
|
||||
void FirmwareSettingsDialog::on_cbRandomizeMAC_toggled()
|
||||
{
|
||||
bool disable = ui->cbRandomizeMAC->isChecked();
|
||||
ui->txtMAC->setDisabled(disable);
|
||||
}
|
||||
|
@ -124,7 +124,6 @@ private slots:
|
||||
|
||||
void on_cbxBirthdayMonth_currentIndexChanged(int idx);
|
||||
void on_overrideFirmwareBox_toggled();
|
||||
void on_cbRandomizeMAC_toggled();
|
||||
|
||||
private:
|
||||
bool verifyMAC();
|
||||
|
@ -7,7 +7,7 @@
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>511</width>
|
||||
<height>342</height>
|
||||
<height>357</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
@ -23,6 +23,13 @@
|
||||
<property name="sizeConstraint">
|
||||
<enum>QLayout::SetFixedSize</enum>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QLabel" name="lblInstanceNum">
|
||||
<property name="text">
|
||||
<string>Configuring settings for instance X</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="grpGeneral">
|
||||
<property name="title">
|
||||
@ -144,9 +151,9 @@
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QCheckBox" name="cbRandomizeMAC">
|
||||
<widget class="QLabel" name="label_6">
|
||||
<property name="text">
|
||||
<string>Randomize</string>
|
||||
<string>(leave empty to use default MAC)</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
|
@ -24,6 +24,7 @@
|
||||
#include <SDL2/SDL.h>
|
||||
|
||||
#include "types.h"
|
||||
#include "Platform.h"
|
||||
#include "Config.h"
|
||||
|
||||
#include "MapButton.h"
|
||||
@ -123,6 +124,12 @@ InputConfigDialog::InputConfigDialog(QWidget* parent) : QDialog(parent), ui(new
|
||||
}
|
||||
|
||||
setupKeypadPage();
|
||||
|
||||
int inst = Platform::InstanceID();
|
||||
if (inst > 0)
|
||||
ui->lblInstanceNum->setText(QString("Configuring mappings for instance %1").arg(inst+1));
|
||||
else
|
||||
ui->lblInstanceNum->hide();
|
||||
}
|
||||
|
||||
InputConfigDialog::~InputConfigDialog()
|
||||
|
@ -7,7 +7,7 @@
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>770</width>
|
||||
<height>719</height>
|
||||
<height>678</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
@ -20,7 +20,7 @@
|
||||
<property name="sizeConstraint">
|
||||
<enum>QLayout::SetFixedSize</enum>
|
||||
</property>
|
||||
<item row="7" column="1">
|
||||
<item row="8" column="1">
|
||||
<widget class="QDialogButtonBox" name="buttonBox">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
@ -30,49 +30,7 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="6" column="0" colspan="2">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Joystick:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QComboBox" name="cbxJoystick">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="whatsThis">
|
||||
<string><html><head/><body><p>Selects which joystick will be used for joystick input, if any is present.</p></body></html></string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="0" column="0" colspan="2">
|
||||
<item row="1" column="0" colspan="2">
|
||||
<widget class="QTabWidget" name="tabWidget">
|
||||
<property name="currentIndex">
|
||||
<number>0</number>
|
||||
@ -167,7 +125,7 @@
|
||||
<widget class="QPushButton" name="btnKeyR">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>76</width>
|
||||
<width>70</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
@ -258,7 +216,7 @@
|
||||
<widget class="QPushButton" name="btnKeyL">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>76</width>
|
||||
<width>70</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
@ -384,7 +342,7 @@
|
||||
<widget class="QPushButton" name="btnKeyX">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>76</width>
|
||||
<width>70</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
@ -464,7 +422,7 @@
|
||||
<widget class="QPushButton" name="btnKeyY">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>76</width>
|
||||
<width>70</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
@ -523,7 +481,7 @@
|
||||
<widget class="QPushButton" name="btnKeyA">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>76</width>
|
||||
<width>70</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
@ -615,7 +573,7 @@
|
||||
<widget class="QPushButton" name="btnKeyB">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>76</width>
|
||||
<width>70</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
@ -698,7 +656,7 @@
|
||||
<widget class="QPushButton" name="btnKeySelect">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>76</width>
|
||||
<width>70</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
@ -757,7 +715,7 @@
|
||||
<widget class="QPushButton" name="btnKeyStart">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>76</width>
|
||||
<width>70</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
@ -882,7 +840,7 @@
|
||||
<widget class="QPushButton" name="btnKeyUp">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>76</width>
|
||||
<width>70</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
@ -962,7 +920,7 @@
|
||||
<widget class="QPushButton" name="btnKeyLeft">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>76</width>
|
||||
<width>70</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
@ -1021,7 +979,7 @@
|
||||
<widget class="QPushButton" name="btnKeyRight">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>76</width>
|
||||
<width>70</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
@ -1113,7 +1071,7 @@
|
||||
<widget class="QPushButton" name="btnKeyDown">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>76</width>
|
||||
<width>70</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
@ -1289,7 +1247,7 @@
|
||||
<widget class="QPushButton" name="btnJoyL">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>76</width>
|
||||
<width>70</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
@ -1441,7 +1399,7 @@
|
||||
<widget class="QPushButton" name="btnJoyUp">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>76</width>
|
||||
<width>70</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
@ -1521,7 +1479,7 @@
|
||||
<widget class="QPushButton" name="btnJoyLeft">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>76</width>
|
||||
<width>70</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
@ -1580,7 +1538,7 @@
|
||||
<widget class="QPushButton" name="btnJoyRight">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>76</width>
|
||||
<width>70</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
@ -1672,7 +1630,7 @@
|
||||
<widget class="QPushButton" name="btnJoyDown">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>76</width>
|
||||
<width>70</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
@ -1814,7 +1772,7 @@
|
||||
<widget class="QPushButton" name="btnJoyX">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>76</width>
|
||||
<width>70</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
@ -1894,7 +1852,7 @@
|
||||
<widget class="QPushButton" name="btnJoyY">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>76</width>
|
||||
<width>70</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
@ -1953,7 +1911,7 @@
|
||||
<widget class="QPushButton" name="btnJoyA">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>76</width>
|
||||
<width>70</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
@ -2045,7 +2003,7 @@
|
||||
<widget class="QPushButton" name="btnJoyB">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>76</width>
|
||||
<width>70</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
@ -2128,7 +2086,7 @@
|
||||
<widget class="QPushButton" name="btnJoySelect">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>76</width>
|
||||
<width>70</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
@ -2187,7 +2145,7 @@
|
||||
<widget class="QPushButton" name="btnJoyStart">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>76</width>
|
||||
<width>70</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
@ -2251,7 +2209,7 @@
|
||||
<widget class="QPushButton" name="btnJoyR">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>76</width>
|
||||
<width>70</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
@ -2321,6 +2279,55 @@
|
||||
</widget>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="7" column="0" colspan="2">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Joystick:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QComboBox" name="cbxJoystick">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="whatsThis">
|
||||
<string><html><head/><body><p>Selects which joystick will be used for joystick input, if any is present.</p></body></html></string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="lblInstanceNum">
|
||||
<property name="text">
|
||||
<string>Configuring mappings for instance X</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources>
|
||||
|
@ -114,6 +114,12 @@ bool TryLoadPCap(void* lib)
|
||||
|
||||
bool Init(bool open_adapter)
|
||||
{
|
||||
PCapAdapter = NULL;
|
||||
PacketLen = 0;
|
||||
RXNum = 0;
|
||||
|
||||
NumAdapters = 0;
|
||||
|
||||
// TODO: how to deal with cases where an adapter is unplugged or changes config??
|
||||
if (!PCapLib)
|
||||
{
|
||||
@ -142,12 +148,6 @@ bool Init(bool open_adapter)
|
||||
}
|
||||
}
|
||||
|
||||
PCapAdapter = NULL;
|
||||
PacketLen = 0;
|
||||
RXNum = 0;
|
||||
|
||||
NumAdapters = 0;
|
||||
|
||||
char errbuf[PCAP_ERRBUF_SIZE];
|
||||
int ret;
|
||||
|
||||
|
634
src/frontend/qt_sdl/LocalMP.cpp
Normal file
634
src/frontend/qt_sdl/LocalMP.cpp
Normal file
@ -0,0 +1,634 @@
|
||||
/*
|
||||
Copyright 2016-2022 melonDS team
|
||||
|
||||
This file is part of melonDS.
|
||||
|
||||
melonDS 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 3 of the License, or (at your option)
|
||||
any later version.
|
||||
|
||||
melonDS 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 melonDS. If not, see http://www.gnu.org/licenses/.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#ifdef __WIN32__
|
||||
#include <windows.h>
|
||||
#else
|
||||
#include <fcntl.h>
|
||||
#include <semaphore.h>
|
||||
#include <time.h>
|
||||
#ifdef __APPLE__
|
||||
#include "sem_timedwait.h"
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#include <string>
|
||||
#include <QSharedMemory>
|
||||
|
||||
#include "Config.h"
|
||||
#include "LocalMP.h"
|
||||
|
||||
|
||||
namespace LocalMP
|
||||
{
|
||||
|
||||
u32 MPUniqueID;
|
||||
u8 PacketBuffer[2048];
|
||||
|
||||
struct MPQueueHeader
|
||||
{
|
||||
u16 NumInstances;
|
||||
u16 InstanceBitmask; // bitmask of all instances present
|
||||
u16 ConnectedBitmask; // bitmask of which instances are ready to send/receive packets
|
||||
u32 PacketWriteOffset;
|
||||
u32 ReplyWriteOffset;
|
||||
u16 MPHostInstanceID; // instance ID from which the last CMD frame was sent
|
||||
u16 MPReplyBitmask; // bitmask of which clients replied in time
|
||||
};
|
||||
|
||||
struct MPPacketHeader
|
||||
{
|
||||
u32 Magic;
|
||||
u32 SenderID;
|
||||
u32 Type; // 0=regular 1=CMD 2=reply 3=ack
|
||||
u32 Length;
|
||||
u64 Timestamp;
|
||||
};
|
||||
|
||||
struct MPSync
|
||||
{
|
||||
u32 Magic;
|
||||
u32 SenderID;
|
||||
u16 ClientMask;
|
||||
u16 Type;
|
||||
u64 Timestamp;
|
||||
};
|
||||
|
||||
QSharedMemory* MPQueue;
|
||||
int InstanceID;
|
||||
u32 PacketReadOffset;
|
||||
u32 ReplyReadOffset;
|
||||
|
||||
const u32 kQueueSize = 0x20000;
|
||||
const u32 kMaxFrameSize = 0x800;
|
||||
const u32 kPacketStart = sizeof(MPQueueHeader);
|
||||
const u32 kReplyStart = kQueueSize / 2;
|
||||
const u32 kPacketEnd = kReplyStart;
|
||||
const u32 kReplyEnd = kQueueSize;
|
||||
|
||||
int RecvTimeout;
|
||||
|
||||
int LastHostID;
|
||||
|
||||
|
||||
// we need to come up with our own abstraction layer for named semaphores
|
||||
// because QSystemSemaphore doesn't support waiting with a timeout
|
||||
// and, as such, is unsuitable to our needs
|
||||
|
||||
#ifdef __WIN32__
|
||||
|
||||
bool SemInited[32];
|
||||
HANDLE SemPool[32];
|
||||
|
||||
void SemPoolInit()
|
||||
{
|
||||
for (int i = 0; i < 32; i++)
|
||||
{
|
||||
SemPool[i] = INVALID_HANDLE_VALUE;
|
||||
SemInited[i] = false;
|
||||
}
|
||||
}
|
||||
|
||||
void SemDeinit(int num);
|
||||
|
||||
void SemPoolDeinit()
|
||||
{
|
||||
for (int i = 0; i < 32; i++)
|
||||
SemDeinit(i);
|
||||
}
|
||||
|
||||
bool SemInit(int num)
|
||||
{
|
||||
if (SemInited[num])
|
||||
return true;
|
||||
|
||||
char semname[64];
|
||||
sprintf(semname, "Local\\melonNIFI_Sem%02d", num);
|
||||
|
||||
HANDLE sem = CreateSemaphore(nullptr, 0, 64, semname);
|
||||
SemPool[num] = sem;
|
||||
SemInited[num] = true;
|
||||
return sem != INVALID_HANDLE_VALUE;
|
||||
}
|
||||
|
||||
void SemDeinit(int num)
|
||||
{
|
||||
if (SemPool[num] != INVALID_HANDLE_VALUE)
|
||||
{
|
||||
CloseHandle(SemPool[num]);
|
||||
SemPool[num] = INVALID_HANDLE_VALUE;
|
||||
}
|
||||
|
||||
SemInited[num] = false;
|
||||
}
|
||||
|
||||
bool SemPost(int num)
|
||||
{
|
||||
SemInit(num);
|
||||
return ReleaseSemaphore(SemPool[num], 1, nullptr) != 0;
|
||||
}
|
||||
|
||||
bool SemWait(int num, int timeout)
|
||||
{
|
||||
return WaitForSingleObject(SemPool[num], timeout) == WAIT_OBJECT_0;
|
||||
}
|
||||
|
||||
void SemReset(int num)
|
||||
{
|
||||
while (WaitForSingleObject(SemPool[num], 0) == WAIT_OBJECT_0);
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
bool SemInited[32];
|
||||
sem_t* SemPool[32];
|
||||
|
||||
void SemPoolInit()
|
||||
{
|
||||
for (int i = 0; i < 32; i++)
|
||||
{
|
||||
SemPool[i] = SEM_FAILED;
|
||||
SemInited[i] = false;
|
||||
}
|
||||
}
|
||||
|
||||
void SemDeinit(int num);
|
||||
|
||||
void SemPoolDeinit()
|
||||
{
|
||||
for (int i = 0; i < 32; i++)
|
||||
SemDeinit(i);
|
||||
}
|
||||
|
||||
bool SemInit(int num)
|
||||
{
|
||||
if (SemInited[num])
|
||||
return true;
|
||||
|
||||
char semname[64];
|
||||
sprintf(semname, "/melonNIFI_Sem%02d", num);
|
||||
|
||||
sem_t* sem = sem_open(semname, O_CREAT, 0644, 0);
|
||||
SemPool[num] = sem;
|
||||
SemInited[num] = true;
|
||||
return sem != SEM_FAILED;
|
||||
}
|
||||
|
||||
void SemDeinit(int num)
|
||||
{
|
||||
if (SemPool[num] != SEM_FAILED)
|
||||
{
|
||||
sem_close(SemPool[num]);
|
||||
SemPool[num] = SEM_FAILED;
|
||||
}
|
||||
|
||||
SemInited[num] = false;
|
||||
}
|
||||
|
||||
bool SemPost(int num)
|
||||
{
|
||||
SemInit(num);
|
||||
return sem_post(SemPool[num]) == 0;
|
||||
}
|
||||
|
||||
bool SemWait(int num, int timeout)
|
||||
{
|
||||
if (!timeout)
|
||||
return sem_trywait(SemPool[num]) == 0;
|
||||
|
||||
struct timespec ts;
|
||||
clock_gettime(CLOCK_REALTIME, &ts);
|
||||
ts.tv_nsec += timeout * 1000000;
|
||||
long sec = ts.tv_nsec / 1000000000;
|
||||
ts.tv_nsec -= sec * 1000000000;
|
||||
ts.tv_sec += sec;
|
||||
|
||||
return sem_timedwait(SemPool[num], &ts) == 0;
|
||||
}
|
||||
|
||||
void SemReset(int num)
|
||||
{
|
||||
while (sem_trywait(SemPool[num]) == 0);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
bool Init()
|
||||
{
|
||||
MPQueue = new QSharedMemory("melonNIFI");
|
||||
|
||||
if (!MPQueue->attach())
|
||||
{
|
||||
printf("MP sharedmem doesn't exist. creating\n");
|
||||
if (!MPQueue->create(kQueueSize))
|
||||
{
|
||||
printf("MP sharedmem create failed :(\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
MPQueue->lock();
|
||||
memset(MPQueue->data(), 0, MPQueue->size());
|
||||
MPQueueHeader* header = (MPQueueHeader*)MPQueue->data();
|
||||
header->PacketWriteOffset = kPacketStart;
|
||||
header->ReplyWriteOffset = kReplyStart;
|
||||
MPQueue->unlock();
|
||||
}
|
||||
|
||||
MPQueue->lock();
|
||||
MPQueueHeader* header = (MPQueueHeader*)MPQueue->data();
|
||||
|
||||
u16 mask = header->InstanceBitmask;
|
||||
for (int i = 0; i < 16; i++)
|
||||
{
|
||||
if (!(mask & (1<<i)))
|
||||
{
|
||||
InstanceID = i;
|
||||
header->InstanceBitmask |= (1<<i);
|
||||
//header->ConnectedBitmask |= (1 << i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
header->NumInstances++;
|
||||
|
||||
PacketReadOffset = header->PacketWriteOffset;
|
||||
ReplyReadOffset = header->ReplyWriteOffset;
|
||||
|
||||
MPQueue->unlock();
|
||||
|
||||
// prepare semaphores
|
||||
// semaphores 0-15: regular frames; semaphore I is posted when instance I needs to process a new frame
|
||||
// semaphores 16-31: MP replies; semaphore I is posted when instance I needs to process a new MP reply
|
||||
|
||||
SemPoolInit();
|
||||
SemInit(InstanceID);
|
||||
SemInit(16+InstanceID);
|
||||
|
||||
LastHostID = -1;
|
||||
|
||||
printf("MP comm init OK, instance ID %d\n", InstanceID);
|
||||
|
||||
RecvTimeout = 25;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void DeInit()
|
||||
{
|
||||
MPQueue->lock();
|
||||
MPQueueHeader* header = (MPQueueHeader*)MPQueue->data();
|
||||
header->ConnectedBitmask &= ~(1 << InstanceID);
|
||||
header->InstanceBitmask &= ~(1 << InstanceID);
|
||||
header->NumInstances--;
|
||||
MPQueue->unlock();
|
||||
|
||||
SemPoolDeinit();
|
||||
|
||||
MPQueue->detach();
|
||||
delete MPQueue;
|
||||
}
|
||||
|
||||
void SetRecvTimeout(int timeout)
|
||||
{
|
||||
RecvTimeout = timeout;
|
||||
}
|
||||
|
||||
void Begin()
|
||||
{
|
||||
MPQueue->lock();
|
||||
MPQueueHeader* header = (MPQueueHeader*)MPQueue->data();
|
||||
PacketReadOffset = header->PacketWriteOffset;
|
||||
ReplyReadOffset = header->ReplyWriteOffset;
|
||||
SemReset(InstanceID);
|
||||
SemReset(16+InstanceID);
|
||||
header->ConnectedBitmask |= (1 << InstanceID);
|
||||
MPQueue->unlock();
|
||||
}
|
||||
|
||||
void End()
|
||||
{
|
||||
MPQueue->lock();
|
||||
MPQueueHeader* header = (MPQueueHeader*)MPQueue->data();
|
||||
//SemReset(InstanceID);
|
||||
//SemReset(16+InstanceID);
|
||||
header->ConnectedBitmask &= ~(1 << InstanceID);
|
||||
MPQueue->unlock();
|
||||
}
|
||||
|
||||
void FIFORead(int fifo, void* buf, int len)
|
||||
{
|
||||
u8* data = (u8*)MPQueue->data();
|
||||
|
||||
u32 offset, start, end;
|
||||
if (fifo == 0)
|
||||
{
|
||||
offset = PacketReadOffset;
|
||||
start = kPacketStart;
|
||||
end = kPacketEnd;
|
||||
}
|
||||
else
|
||||
{
|
||||
offset = ReplyReadOffset;
|
||||
start = kReplyStart;
|
||||
end = kReplyEnd;
|
||||
}
|
||||
|
||||
if ((offset + len) >= end)
|
||||
{
|
||||
u32 part1 = end - offset;
|
||||
memcpy(buf, &data[offset], part1);
|
||||
memcpy(&((u8*)buf)[part1], &data[start], len - part1);
|
||||
offset = start + len - part1;
|
||||
}
|
||||
else
|
||||
{
|
||||
memcpy(buf, &data[offset], len);
|
||||
offset += len;
|
||||
}
|
||||
|
||||
if (fifo == 0) PacketReadOffset = offset;
|
||||
else ReplyReadOffset = offset;
|
||||
}
|
||||
|
||||
void FIFOWrite(int fifo, void* buf, int len)
|
||||
{
|
||||
u8* data = (u8*)MPQueue->data();
|
||||
MPQueueHeader* header = (MPQueueHeader*)&data[0];
|
||||
|
||||
u32 offset, start, end;
|
||||
if (fifo == 0)
|
||||
{
|
||||
offset = header->PacketWriteOffset;
|
||||
start = kPacketStart;
|
||||
end = kPacketEnd;
|
||||
}
|
||||
else
|
||||
{
|
||||
offset = header->ReplyWriteOffset;
|
||||
start = kReplyStart;
|
||||
end = kReplyEnd;
|
||||
}
|
||||
|
||||
if ((offset + len) >= end)
|
||||
{
|
||||
u32 part1 = end - offset;
|
||||
memcpy(&data[offset], buf, part1);
|
||||
memcpy(&data[start], &((u8*)buf)[part1], len - part1);
|
||||
offset = start + len - part1;
|
||||
}
|
||||
else
|
||||
{
|
||||
memcpy(&data[offset], buf, len);
|
||||
offset += len;
|
||||
}
|
||||
|
||||
if (fifo == 0) header->PacketWriteOffset = offset;
|
||||
else header->ReplyWriteOffset = offset;
|
||||
}
|
||||
|
||||
int SendPacketGeneric(u32 type, u8* packet, int len, u64 timestamp)
|
||||
{
|
||||
MPQueue->lock();
|
||||
u8* data = (u8*)MPQueue->data();
|
||||
MPQueueHeader* header = (MPQueueHeader*)&data[0];
|
||||
|
||||
u16 mask = header->ConnectedBitmask;
|
||||
|
||||
// TODO: check if the FIFO is full!
|
||||
|
||||
MPPacketHeader pktheader;
|
||||
pktheader.Magic = 0x4946494E;
|
||||
pktheader.SenderID = InstanceID;
|
||||
pktheader.Type = type;
|
||||
pktheader.Length = len;
|
||||
pktheader.Timestamp = timestamp;
|
||||
|
||||
type &= 0xFFFF;
|
||||
int nfifo = (type == 2) ? 1 : 0;
|
||||
FIFOWrite(nfifo, &pktheader, sizeof(pktheader));
|
||||
if (len)
|
||||
FIFOWrite(nfifo, packet, len);
|
||||
|
||||
if (type == 1)
|
||||
{
|
||||
// NOTE: this is not guarded against, say, multiple multiplay games happening on the same machine
|
||||
// we would need to pass the packet's SenderID through the wifi module for that
|
||||
header->MPHostInstanceID = InstanceID;
|
||||
header->MPReplyBitmask = 0;
|
||||
ReplyReadOffset = header->ReplyWriteOffset;
|
||||
SemReset(16 + InstanceID);
|
||||
}
|
||||
else if (type == 2)
|
||||
{
|
||||
header->MPReplyBitmask |= (1 << InstanceID);
|
||||
}
|
||||
|
||||
MPQueue->unlock();
|
||||
|
||||
if (type == 2)
|
||||
{
|
||||
SemPost(16 + header->MPHostInstanceID);
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = 0; i < 16; i++)
|
||||
{
|
||||
if (mask & (1<<i))
|
||||
SemPost(i);
|
||||
}
|
||||
}
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
int RecvPacketGeneric(u8* packet, bool block, u64* timestamp)
|
||||
{
|
||||
for (;;)
|
||||
{
|
||||
if (!SemWait(InstanceID, block ? RecvTimeout : 0))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
MPQueue->lock();
|
||||
u8* data = (u8*)MPQueue->data();
|
||||
MPQueueHeader* header = (MPQueueHeader*)&data[0];
|
||||
|
||||
MPPacketHeader pktheader;
|
||||
FIFORead(0, &pktheader, sizeof(pktheader));
|
||||
|
||||
if (pktheader.Magic != 0x4946494E)
|
||||
{
|
||||
printf("PACKET FIFO OVERFLOW\n");
|
||||
PacketReadOffset = header->PacketWriteOffset;
|
||||
SemReset(InstanceID);
|
||||
MPQueue->unlock();
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (pktheader.SenderID == InstanceID)
|
||||
{
|
||||
// skip this packet
|
||||
PacketReadOffset += pktheader.Length;
|
||||
if (PacketReadOffset >= kPacketEnd)
|
||||
PacketReadOffset += kPacketStart - kPacketEnd;
|
||||
|
||||
MPQueue->unlock();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (pktheader.Length)
|
||||
{
|
||||
FIFORead(0, packet, pktheader.Length);
|
||||
|
||||
if (pktheader.Type == 1)
|
||||
LastHostID = pktheader.SenderID;
|
||||
}
|
||||
|
||||
if (timestamp) *timestamp = pktheader.Timestamp;
|
||||
MPQueue->unlock();
|
||||
return pktheader.Length;
|
||||
}
|
||||
}
|
||||
|
||||
int SendPacket(u8* packet, int len, u64 timestamp)
|
||||
{
|
||||
return SendPacketGeneric(0, packet, len, timestamp);
|
||||
}
|
||||
|
||||
int RecvPacket(u8* packet, u64* timestamp)
|
||||
{
|
||||
return RecvPacketGeneric(packet, false, timestamp);
|
||||
}
|
||||
|
||||
|
||||
int SendCmd(u8* packet, int len, u64 timestamp)
|
||||
{
|
||||
return SendPacketGeneric(1, packet, len, timestamp);
|
||||
}
|
||||
|
||||
int SendReply(u8* packet, int len, u64 timestamp, u16 aid)
|
||||
{
|
||||
return SendPacketGeneric(2 | (aid<<16), packet, len, timestamp);
|
||||
}
|
||||
|
||||
int SendAck(u8* packet, int len, u64 timestamp)
|
||||
{
|
||||
return SendPacketGeneric(3, packet, len, timestamp);
|
||||
}
|
||||
|
||||
int RecvHostPacket(u8* packet, u64* timestamp)
|
||||
{
|
||||
if (LastHostID != -1)
|
||||
{
|
||||
// check if the host is still connected
|
||||
|
||||
MPQueue->lock();
|
||||
u8* data = (u8*)MPQueue->data();
|
||||
MPQueueHeader* header = (MPQueueHeader*)&data[0];
|
||||
u16 curinstmask = header->ConnectedBitmask;
|
||||
MPQueue->unlock();
|
||||
|
||||
if (!(curinstmask & (1 << LastHostID)))
|
||||
return -1;
|
||||
}
|
||||
|
||||
return RecvPacketGeneric(packet, true, timestamp);
|
||||
}
|
||||
|
||||
u16 RecvReplies(u8* packets, u64 timestamp, u16 aidmask)
|
||||
{
|
||||
u16 ret = 0;
|
||||
u16 myinstmask = (1 << InstanceID);
|
||||
u16 curinstmask;
|
||||
|
||||
{
|
||||
MPQueue->lock();
|
||||
u8* data = (u8*)MPQueue->data();
|
||||
MPQueueHeader* header = (MPQueueHeader*)&data[0];
|
||||
curinstmask = header->ConnectedBitmask;
|
||||
MPQueue->unlock();
|
||||
}
|
||||
|
||||
// if all clients have left: return early
|
||||
if ((myinstmask & curinstmask) == curinstmask)
|
||||
return 0;
|
||||
|
||||
for (;;)
|
||||
{
|
||||
if (!SemWait(16+InstanceID, RecvTimeout))
|
||||
{
|
||||
// no more replies available
|
||||
return ret;
|
||||
}
|
||||
|
||||
MPQueue->lock();
|
||||
u8* data = (u8*)MPQueue->data();
|
||||
MPQueueHeader* header = (MPQueueHeader*)&data[0];
|
||||
|
||||
MPPacketHeader pktheader;
|
||||
FIFORead(1, &pktheader, sizeof(pktheader));
|
||||
|
||||
if (pktheader.Magic != 0x4946494E)
|
||||
{
|
||||
printf("REPLY FIFO OVERFLOW\n");
|
||||
ReplyReadOffset = header->ReplyWriteOffset;
|
||||
SemReset(16+InstanceID);
|
||||
MPQueue->unlock();
|
||||
return 0;
|
||||
}
|
||||
|
||||
if ((pktheader.SenderID == InstanceID) || // packet we sent out (shouldn't happen, but hey)
|
||||
(pktheader.Timestamp < (timestamp - 32))) // stale packet
|
||||
{
|
||||
// skip this packet
|
||||
ReplyReadOffset += pktheader.Length;
|
||||
if (ReplyReadOffset >= kReplyEnd)
|
||||
ReplyReadOffset += kReplyStart - kReplyEnd;
|
||||
|
||||
MPQueue->unlock();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (pktheader.Length)
|
||||
{
|
||||
u32 aid = (pktheader.Type >> 16);
|
||||
FIFORead(1, &packets[(aid-1)*1024], pktheader.Length);
|
||||
ret |= (1 << aid);
|
||||
}
|
||||
|
||||
myinstmask |= (1 << pktheader.SenderID);
|
||||
if (((myinstmask & curinstmask) == curinstmask) ||
|
||||
((ret & aidmask) == aidmask))
|
||||
{
|
||||
// all the clients have sent their reply
|
||||
|
||||
MPQueue->unlock();
|
||||
return ret;
|
||||
}
|
||||
|
||||
MPQueue->unlock();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
45
src/frontend/qt_sdl/LocalMP.h
Normal file
45
src/frontend/qt_sdl/LocalMP.h
Normal file
@ -0,0 +1,45 @@
|
||||
/*
|
||||
Copyright 2016-2022 melonDS team
|
||||
|
||||
This file is part of melonDS.
|
||||
|
||||
melonDS 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 3 of the License, or (at your option)
|
||||
any later version.
|
||||
|
||||
melonDS 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 melonDS. If not, see http://www.gnu.org/licenses/.
|
||||
*/
|
||||
|
||||
#ifndef LOCALMP_H
|
||||
#define LOCALMP_H
|
||||
|
||||
#include "types.h"
|
||||
|
||||
namespace LocalMP
|
||||
{
|
||||
|
||||
bool Init();
|
||||
void DeInit();
|
||||
|
||||
void SetRecvTimeout(int timeout);
|
||||
|
||||
void Begin();
|
||||
void End();
|
||||
|
||||
int SendPacket(u8* data, int len, u64 timestamp);
|
||||
int RecvPacket(u8* data, u64* timestamp);
|
||||
int SendCmd(u8* data, int len, u64 timestamp);
|
||||
int SendReply(u8* data, int len, u64 timestamp, u16 aid);
|
||||
int SendAck(u8* data, int len, u64 timestamp);
|
||||
int RecvHostPacket(u8* data, u64* timestamp);
|
||||
u16 RecvReplies(u8* data, u64 timestamp, u16 aidmask);
|
||||
|
||||
}
|
||||
|
||||
#endif // LOCALMP_H
|
73
src/frontend/qt_sdl/MPSettingsDialog.cpp
Normal file
73
src/frontend/qt_sdl/MPSettingsDialog.cpp
Normal file
@ -0,0 +1,73 @@
|
||||
/*
|
||||
Copyright 2016-2022 melonDS team
|
||||
|
||||
This file is part of melonDS.
|
||||
|
||||
melonDS 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 3 of the License, or (at your option)
|
||||
any later version.
|
||||
|
||||
melonDS 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 melonDS. If not, see http://www.gnu.org/licenses/.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <QMessageBox>
|
||||
|
||||
#include "types.h"
|
||||
#include "Platform.h"
|
||||
#include "Config.h"
|
||||
|
||||
#include "LAN_Socket.h"
|
||||
#include "LAN_PCap.h"
|
||||
#include "Wifi.h"
|
||||
|
||||
#include "MPSettingsDialog.h"
|
||||
#include "ui_MPSettingsDialog.h"
|
||||
|
||||
|
||||
MPSettingsDialog* MPSettingsDialog::currentDlg = nullptr;
|
||||
|
||||
extern bool RunningSomething;
|
||||
|
||||
|
||||
MPSettingsDialog::MPSettingsDialog(QWidget* parent) : QDialog(parent), ui(new Ui::MPSettingsDialog)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
setAttribute(Qt::WA_DeleteOnClose);
|
||||
|
||||
grpAudioMode = new QButtonGroup(this);
|
||||
grpAudioMode->addButton(ui->rbAudioAll, 0);
|
||||
grpAudioMode->addButton(ui->rbAudioOneOnly, 1);
|
||||
grpAudioMode->addButton(ui->rbAudioActiveOnly, 2);
|
||||
grpAudioMode->button(Config::MPAudioMode)->setChecked(true);
|
||||
|
||||
ui->sbReceiveTimeout->setValue(Config::MPRecvTimeout);
|
||||
}
|
||||
|
||||
MPSettingsDialog::~MPSettingsDialog()
|
||||
{
|
||||
delete ui;
|
||||
}
|
||||
|
||||
void MPSettingsDialog::done(int r)
|
||||
{
|
||||
if (r == QDialog::Accepted)
|
||||
{
|
||||
Config::MPAudioMode = grpAudioMode->checkedId();
|
||||
Config::MPRecvTimeout = ui->sbReceiveTimeout->value();
|
||||
|
||||
Config::Save();
|
||||
}
|
||||
|
||||
QDialog::done(r);
|
||||
|
||||
closeDlg();
|
||||
}
|
||||
|
||||
//
|
65
src/frontend/qt_sdl/MPSettingsDialog.h
Normal file
65
src/frontend/qt_sdl/MPSettingsDialog.h
Normal file
@ -0,0 +1,65 @@
|
||||
/*
|
||||
Copyright 2016-2022 melonDS team
|
||||
|
||||
This file is part of melonDS.
|
||||
|
||||
melonDS 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 3 of the License, or (at your option)
|
||||
any later version.
|
||||
|
||||
melonDS 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 melonDS. If not, see http://www.gnu.org/licenses/.
|
||||
*/
|
||||
|
||||
#ifndef MPSETTINGSDIALOG_H
|
||||
#define MPSETTINGSDIALOG_H
|
||||
|
||||
#include <QDialog>
|
||||
#include <QButtonGroup>
|
||||
|
||||
namespace Ui { class MPSettingsDialog; }
|
||||
class MPSettingsDialog;
|
||||
|
||||
class MPSettingsDialog : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit MPSettingsDialog(QWidget* parent);
|
||||
~MPSettingsDialog();
|
||||
|
||||
static MPSettingsDialog* currentDlg;
|
||||
static MPSettingsDialog* openDlg(QWidget* parent)
|
||||
{
|
||||
if (currentDlg)
|
||||
{
|
||||
currentDlg->activateWindow();
|
||||
return currentDlg;
|
||||
}
|
||||
|
||||
currentDlg = new MPSettingsDialog(parent);
|
||||
currentDlg->open();
|
||||
return currentDlg;
|
||||
}
|
||||
static void closeDlg()
|
||||
{
|
||||
currentDlg = nullptr;
|
||||
}
|
||||
|
||||
private slots:
|
||||
void done(int r);
|
||||
|
||||
//
|
||||
|
||||
private:
|
||||
Ui::MPSettingsDialog* ui;
|
||||
|
||||
QButtonGroup* grpAudioMode;
|
||||
};
|
||||
|
||||
#endif // MPSETTINGSDIALOG_H
|
142
src/frontend/qt_sdl/MPSettingsDialog.ui
Normal file
142
src/frontend/qt_sdl/MPSettingsDialog.ui
Normal file
@ -0,0 +1,142 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>MPSettingsDialog</class>
|
||||
<widget class="QDialog" name="MPSettingsDialog">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>466</width>
|
||||
<height>202</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Multiplayer settings - melonDS</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox">
|
||||
<property name="title">
|
||||
<string>Audio output</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_2">
|
||||
<item row="1" column="0">
|
||||
<widget class="QRadioButton" name="rbAudioOneOnly">
|
||||
<property name="text">
|
||||
<string>Instance 1 only</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QRadioButton" name="rbAudioAll">
|
||||
<property name="text">
|
||||
<string>All instances</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QRadioButton" name="rbAudioActiveOnly">
|
||||
<property name="text">
|
||||
<string>Active instance only</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox_2">
|
||||
<property name="title">
|
||||
<string>Network</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="0" column="1">
|
||||
<widget class="QSpinBox" name="sbReceiveTimeout">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>50</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>1000</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Data reception timeout: </string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="2">
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
|
||||
<horstretch>1</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>milliseconds</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QDialogButtonBox" name="buttonBox">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="standardButtons">
|
||||
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>accepted()</signal>
|
||||
<receiver>MPSettingsDialog</receiver>
|
||||
<slot>accept()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>248</x>
|
||||
<y>254</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>157</x>
|
||||
<y>274</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>rejected()</signal>
|
||||
<receiver>MPSettingsDialog</receiver>
|
||||
<slot>reject()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>316</x>
|
||||
<y>260</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>286</x>
|
||||
<y>274</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
</connections>
|
||||
</ui>
|
@ -22,6 +22,7 @@
|
||||
|
||||
#include "types.h"
|
||||
#include "Config.h"
|
||||
#include "Platform.h"
|
||||
|
||||
#include "PathSettingsDialog.h"
|
||||
#include "ui_PathSettingsDialog.h"
|
||||
@ -43,6 +44,12 @@ PathSettingsDialog::PathSettingsDialog(QWidget* parent) : QDialog(parent), ui(ne
|
||||
ui->txtSaveFilePath->setText(QString::fromStdString(Config::SaveFilePath));
|
||||
ui->txtSavestatePath->setText(QString::fromStdString(Config::SavestatePath));
|
||||
ui->txtCheatFilePath->setText(QString::fromStdString(Config::CheatFilePath));
|
||||
|
||||
int inst = Platform::InstanceID();
|
||||
if (inst > 0)
|
||||
ui->lblInstanceNum->setText(QString("Configuring paths for instance %1").arg(inst+1));
|
||||
else
|
||||
ui->lblInstanceNum->hide();
|
||||
}
|
||||
|
||||
PathSettingsDialog::~PathSettingsDialog()
|
||||
|
@ -7,49 +7,63 @@
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>439</width>
|
||||
<height>166</height>
|
||||
<height>185</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Path settings - melonDS</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="0" column="1">
|
||||
<widget class="QLineEdit" name="txtSaveFilePath">
|
||||
<property name="clearButtonEnabled">
|
||||
<bool>true</bool>
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="label_4">
|
||||
<property name="text">
|
||||
<string>Cheat files path:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="2">
|
||||
<item row="3" column="2">
|
||||
<widget class="QPushButton" name="btnCheatFileBrowse">
|
||||
<property name="text">
|
||||
<string>Browse...</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="label_3">
|
||||
<property name="text">
|
||||
<string>Savestates path:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="0" colspan="3">
|
||||
<widget class="QLabel" name="label_5">
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QLineEdit" name="txtSavestatePath">
|
||||
<property name="clearButtonEnabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="2">
|
||||
<widget class="QPushButton" name="btnSaveFileBrowse">
|
||||
<property name="text">
|
||||
<string>Browse...</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="QLineEdit" name="txtCheatFilePath">
|
||||
<property name="clearButtonEnabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0" colspan="3">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Leave a path blank to use the current ROM's path.</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="0" colspan="3">
|
||||
<item row="6" column="0" colspan="3">
|
||||
<widget class="QDialogButtonBox" name="buttonBox">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
@ -59,35 +73,14 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="label_4">
|
||||
<property name="text">
|
||||
<string>Cheat files path:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="2">
|
||||
<widget class="QPushButton" name="btnSaveFileBrowse">
|
||||
<property name="text">
|
||||
<string>Browse...</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="2">
|
||||
<item row="2" column="2">
|
||||
<widget class="QPushButton" name="btnSavestateBrowse">
|
||||
<property name="text">
|
||||
<string>Browse...</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QLineEdit" name="txtSavestatePath">
|
||||
<property name="clearButtonEnabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string>Save files path:</string>
|
||||
@ -95,9 +88,23 @@
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="0" colspan="3">
|
||||
<widget class="QLabel" name="label_5">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string/>
|
||||
<string>Leave a path blank to use the current ROM's path.</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QLineEdit" name="txtSaveFilePath">
|
||||
<property name="clearButtonEnabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0" colspan="3">
|
||||
<widget class="QLabel" name="lblInstanceNum">
|
||||
<property name="text">
|
||||
<string>Configuring paths for instance X</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
|
@ -20,28 +20,7 @@
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#ifdef __WIN32__
|
||||
#define NTDDI_VERSION 0x06000000 // GROSS FUCKING HACK
|
||||
#include <winsock2.h>
|
||||
#include <windows.h>
|
||||
//#include <knownfolders.h> // FUCK THAT SHIT
|
||||
#include <shlobj.h>
|
||||
#include <ws2tcpip.h>
|
||||
#include <io.h>
|
||||
#define dup _dup
|
||||
#define socket_t SOCKET
|
||||
#define sockaddr_t SOCKADDR
|
||||
#else
|
||||
#include <unistd.h>
|
||||
#include <netinet/in.h>
|
||||
#include <sys/select.h>
|
||||
#include <sys/socket.h>
|
||||
|
||||
#define socket_t int
|
||||
#define sockaddr_t struct sockaddr
|
||||
#define closesocket close
|
||||
#endif
|
||||
|
||||
#include <string>
|
||||
#include <QStandardPaths>
|
||||
#include <QString>
|
||||
#include <QDir>
|
||||
@ -49,32 +28,83 @@
|
||||
#include <QSemaphore>
|
||||
#include <QMutex>
|
||||
#include <QOpenGLContext>
|
||||
#include <QSharedMemory>
|
||||
|
||||
#include "Platform.h"
|
||||
#include "Config.h"
|
||||
#include "ROMManager.h"
|
||||
#include "CameraManager.h"
|
||||
#include "LAN_Socket.h"
|
||||
#include "LAN_PCap.h"
|
||||
#include <string>
|
||||
|
||||
#ifndef INVALID_SOCKET
|
||||
#define INVALID_SOCKET (socket_t)-1
|
||||
#endif
|
||||
#include "LocalMP.h"
|
||||
|
||||
|
||||
std::string EmuDirectory;
|
||||
|
||||
extern CameraManager* camManager[2];
|
||||
|
||||
void emuStop();
|
||||
|
||||
|
||||
namespace Platform
|
||||
{
|
||||
|
||||
socket_t MPSocket;
|
||||
sockaddr_t MPSendAddr;
|
||||
u8 PacketBuffer[2048];
|
||||
QSharedMemory* IPCBuffer = nullptr;
|
||||
int IPCInstanceID;
|
||||
|
||||
#define NIFI_VER 1
|
||||
void IPCInit()
|
||||
{
|
||||
IPCInstanceID = 0;
|
||||
|
||||
IPCBuffer = new QSharedMemory("melonIPC");
|
||||
|
||||
if (!IPCBuffer->attach())
|
||||
{
|
||||
printf("IPC sharedmem doesn't exist. creating\n");
|
||||
if (!IPCBuffer->create(1024))
|
||||
{
|
||||
printf("IPC sharedmem create failed :(\n");
|
||||
delete IPCBuffer;
|
||||
IPCBuffer = nullptr;
|
||||
return;
|
||||
}
|
||||
|
||||
IPCBuffer->lock();
|
||||
memset(IPCBuffer->data(), 0, IPCBuffer->size());
|
||||
IPCBuffer->unlock();
|
||||
}
|
||||
|
||||
IPCBuffer->lock();
|
||||
u8* data = (u8*)IPCBuffer->data();
|
||||
u16 mask = *(u16*)&data[0];
|
||||
for (int i = 0; i < 16; i++)
|
||||
{
|
||||
if (!(mask & (1<<i)))
|
||||
{
|
||||
IPCInstanceID = i;
|
||||
*(u16*)&data[0] |= (1<<i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
IPCBuffer->unlock();
|
||||
|
||||
printf("IPC: instance ID %d\n", IPCInstanceID);
|
||||
}
|
||||
|
||||
void IPCDeInit()
|
||||
{
|
||||
if (IPCBuffer)
|
||||
{
|
||||
IPCBuffer->lock();
|
||||
u8* data = (u8*)IPCBuffer->data();
|
||||
*(u16*)&data[0] &= ~(1<<IPCInstanceID);
|
||||
IPCBuffer->unlock();
|
||||
|
||||
IPCBuffer->detach();
|
||||
delete IPCBuffer;
|
||||
}
|
||||
IPCBuffer = nullptr;
|
||||
}
|
||||
|
||||
|
||||
void Init(int argc, char** argv)
|
||||
@ -110,10 +140,13 @@ void Init(int argc, char** argv)
|
||||
confdir = config.absolutePath() + "/melonDS/";
|
||||
EmuDirectory = confdir.toStdString();
|
||||
#endif
|
||||
|
||||
IPCInit();
|
||||
}
|
||||
|
||||
void DeInit()
|
||||
{
|
||||
IPCDeInit();
|
||||
}
|
||||
|
||||
|
||||
@ -123,6 +156,22 @@ void StopEmu()
|
||||
}
|
||||
|
||||
|
||||
int InstanceID()
|
||||
{
|
||||
return IPCInstanceID;
|
||||
}
|
||||
|
||||
std::string InstanceFileSuffix()
|
||||
{
|
||||
int inst = IPCInstanceID;
|
||||
if (inst == 0) return "";
|
||||
|
||||
char suffix[16] = {0};
|
||||
snprintf(suffix, 15, ".%d", inst+1);
|
||||
return suffix;
|
||||
}
|
||||
|
||||
|
||||
int GetConfigInt(ConfigEntry entry)
|
||||
{
|
||||
const int imgsizes[] = {0, 256, 512, 1024, 2048, 4096};
|
||||
@ -169,7 +218,6 @@ bool GetConfigBool(ConfigEntry entry)
|
||||
case DSiSD_ReadOnly: return Config::DSiSDReadOnly != 0;
|
||||
case DSiSD_FolderSync: return Config::DSiSDFolderSync != 0;
|
||||
|
||||
case Firm_RandomizeMAC: return Config::RandomizeMAC != 0;
|
||||
case Firm_OverrideSettings: return Config::FirmwareOverrideSettings != 0;
|
||||
}
|
||||
|
||||
@ -372,6 +420,11 @@ bool Mutex_TryLock(Mutex* mutex)
|
||||
return ((QMutex*) mutex)->try_lock();
|
||||
}
|
||||
|
||||
void Sleep(u64 usecs)
|
||||
{
|
||||
QThread::usleep(usecs);
|
||||
}
|
||||
|
||||
|
||||
void WriteNDSSave(const u8* savedata, u32 savelen, u32 writeoffset, u32 writelen)
|
||||
{
|
||||
@ -386,149 +439,61 @@ void WriteGBASave(const u8* savedata, u32 savelen, u32 writeoffset, u32 writelen
|
||||
}
|
||||
|
||||
|
||||
|
||||
bool MP_Init()
|
||||
{
|
||||
int opt_true = 1;
|
||||
int res;
|
||||
|
||||
#ifdef __WIN32__
|
||||
WSADATA wsadata;
|
||||
if (WSAStartup(MAKEWORD(2, 2), &wsadata) != 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
#endif // __WIN32__
|
||||
|
||||
MPSocket = socket(AF_INET, SOCK_DGRAM, 0);
|
||||
if (MPSocket < 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
res = setsockopt(MPSocket, SOL_SOCKET, SO_REUSEADDR, (const char*)&opt_true, sizeof(int));
|
||||
if (res < 0)
|
||||
{
|
||||
closesocket(MPSocket);
|
||||
MPSocket = INVALID_SOCKET;
|
||||
return false;
|
||||
}
|
||||
|
||||
#if defined(BSD) || defined(__APPLE__)
|
||||
res = setsockopt(MPSocket, SOL_SOCKET, SO_REUSEPORT, (const char*)&opt_true, sizeof(int));
|
||||
if (res < 0)
|
||||
{
|
||||
closesocket(MPSocket);
|
||||
MPSocket = INVALID_SOCKET;
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
sockaddr_t saddr;
|
||||
saddr.sa_family = AF_INET;
|
||||
*(u32*)&saddr.sa_data[2] = htonl(Config::SocketBindAnyAddr ? INADDR_ANY : INADDR_LOOPBACK);
|
||||
*(u16*)&saddr.sa_data[0] = htons(7064);
|
||||
res = bind(MPSocket, &saddr, sizeof(sockaddr_t));
|
||||
if (res < 0)
|
||||
{
|
||||
closesocket(MPSocket);
|
||||
MPSocket = INVALID_SOCKET;
|
||||
return false;
|
||||
}
|
||||
|
||||
res = setsockopt(MPSocket, SOL_SOCKET, SO_BROADCAST, (const char*)&opt_true, sizeof(int));
|
||||
if (res < 0)
|
||||
{
|
||||
closesocket(MPSocket);
|
||||
MPSocket = INVALID_SOCKET;
|
||||
return false;
|
||||
}
|
||||
|
||||
MPSendAddr.sa_family = AF_INET;
|
||||
*(u32*)&MPSendAddr.sa_data[2] = htonl(INADDR_BROADCAST);
|
||||
*(u16*)&MPSendAddr.sa_data[0] = htons(7064);
|
||||
|
||||
return true;
|
||||
return LocalMP::Init();
|
||||
}
|
||||
|
||||
void MP_DeInit()
|
||||
{
|
||||
if (MPSocket >= 0)
|
||||
closesocket(MPSocket);
|
||||
|
||||
#ifdef __WIN32__
|
||||
WSACleanup();
|
||||
#endif // __WIN32__
|
||||
return LocalMP::DeInit();
|
||||
}
|
||||
|
||||
int MP_SendPacket(u8* data, int len)
|
||||
void MP_Begin()
|
||||
{
|
||||
if (MPSocket < 0)
|
||||
return 0;
|
||||
|
||||
if (len > 2048-8)
|
||||
{
|
||||
printf("MP_SendPacket: error: packet too long (%d)\n", len);
|
||||
return 0;
|
||||
}
|
||||
|
||||
*(u32*)&PacketBuffer[0] = htonl(0x4946494E); // NIFI
|
||||
PacketBuffer[4] = NIFI_VER;
|
||||
PacketBuffer[5] = 0;
|
||||
*(u16*)&PacketBuffer[6] = htons(len);
|
||||
memcpy(&PacketBuffer[8], data, len);
|
||||
|
||||
int slen = sendto(MPSocket, (const char*)PacketBuffer, len+8, 0, &MPSendAddr, sizeof(sockaddr_t));
|
||||
if (slen < 8) return 0;
|
||||
return slen - 8;
|
||||
return LocalMP::Begin();
|
||||
}
|
||||
|
||||
int MP_RecvPacket(u8* data, bool block)
|
||||
void MP_End()
|
||||
{
|
||||
if (MPSocket < 0)
|
||||
return 0;
|
||||
|
||||
fd_set fd;
|
||||
struct timeval tv;
|
||||
|
||||
FD_ZERO(&fd);
|
||||
FD_SET(MPSocket, &fd);
|
||||
tv.tv_sec = 0;
|
||||
tv.tv_usec = block ? 5000 : 0;
|
||||
|
||||
if (!select(MPSocket+1, &fd, 0, 0, &tv))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
sockaddr_t fromAddr;
|
||||
socklen_t fromLen = sizeof(sockaddr_t);
|
||||
int rlen = recvfrom(MPSocket, (char*)PacketBuffer, 2048, 0, &fromAddr, &fromLen);
|
||||
if (rlen < 8+24)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
rlen -= 8;
|
||||
|
||||
if (ntohl(*(u32*)&PacketBuffer[0]) != 0x4946494E)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (PacketBuffer[4] != NIFI_VER)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (ntohs(*(u16*)&PacketBuffer[6]) != rlen)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
memcpy(data, &PacketBuffer[8], rlen);
|
||||
return rlen;
|
||||
return LocalMP::End();
|
||||
}
|
||||
|
||||
int MP_SendPacket(u8* data, int len, u64 timestamp)
|
||||
{
|
||||
return LocalMP::SendPacket(data, len, timestamp);
|
||||
}
|
||||
|
||||
int MP_RecvPacket(u8* data, u64* timestamp)
|
||||
{
|
||||
return LocalMP::RecvPacket(data, timestamp);
|
||||
}
|
||||
|
||||
int MP_SendCmd(u8* data, int len, u64 timestamp)
|
||||
{
|
||||
return LocalMP::SendCmd(data, len, timestamp);
|
||||
}
|
||||
|
||||
int MP_SendReply(u8* data, int len, u64 timestamp, u16 aid)
|
||||
{
|
||||
return LocalMP::SendReply(data, len, timestamp, aid);
|
||||
}
|
||||
|
||||
int MP_SendAck(u8* data, int len, u64 timestamp)
|
||||
{
|
||||
return LocalMP::SendAck(data, len, timestamp);
|
||||
}
|
||||
|
||||
int MP_RecvHostPacket(u8* data, u64* timestamp)
|
||||
{
|
||||
return LocalMP::RecvHostPacket(data, timestamp);
|
||||
}
|
||||
|
||||
u16 MP_RecvReplies(u8* data, u64 timestamp, u16 aidmask)
|
||||
{
|
||||
return LocalMP::RecvReplies(data, timestamp, aidmask);
|
||||
}
|
||||
|
||||
bool LAN_Init()
|
||||
{
|
||||
@ -573,9 +538,20 @@ int LAN_RecvPacket(u8* data)
|
||||
return LAN_Socket::RecvPacket(data);
|
||||
}
|
||||
|
||||
void Sleep(u64 usecs)
|
||||
|
||||
void Camera_Start(int num)
|
||||
{
|
||||
QThread::usleep(usecs);
|
||||
return camManager[num]->start();
|
||||
}
|
||||
|
||||
void Camera_Stop(int num)
|
||||
{
|
||||
return camManager[num]->stop();
|
||||
}
|
||||
|
||||
void Camera_CaptureFrame(int num, u32* frame, int width, int height, bool yuv)
|
||||
{
|
||||
return camManager[num]->captureFrame(frame, width, height, yuv);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -23,6 +23,7 @@
|
||||
#include "DSi_I2C.h"
|
||||
#include "NDS.h"
|
||||
#include "Config.h"
|
||||
#include "Platform.h"
|
||||
|
||||
#include "types.h"
|
||||
|
||||
@ -65,6 +66,12 @@ PowerManagementDialog::PowerManagementDialog(QWidget* parent) : QDialog(parent),
|
||||
}
|
||||
ui->sliderDSiBatteryLevel->setValue(dsiBatterySliderPos);
|
||||
|
||||
int inst = Platform::InstanceID();
|
||||
if (inst > 0)
|
||||
ui->lblInstanceNum->setText(QString("Setting battery levels for instance %1").arg(inst+1));
|
||||
else
|
||||
ui->lblInstanceNum->hide();
|
||||
|
||||
inited = true;
|
||||
}
|
||||
|
||||
|
@ -7,7 +7,7 @@
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>562</width>
|
||||
<height>279</height>
|
||||
<height>288</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
@ -23,37 +23,7 @@
|
||||
<property name="sizeConstraint">
|
||||
<enum>QLayout::SetFixedSize</enum>
|
||||
</property>
|
||||
<item row="0" column="0">
|
||||
<widget class="QGroupBox" name="grpDSBattery">
|
||||
<property name="title">
|
||||
<string>DS Battery</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_2">
|
||||
<item row="1" column="1">
|
||||
<widget class="QRadioButton" name="rbDSBatteryLow">
|
||||
<property name="text">
|
||||
<string>Low</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0" rowspan="2">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Battery Level</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QRadioButton" name="rbDSBatteryOkay">
|
||||
<property name="text">
|
||||
<string>Okay</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<item row="4" column="0">
|
||||
<widget class="QDialogButtonBox" name="buttonBox">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
@ -63,7 +33,7 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<item row="3" column="0">
|
||||
<widget class="QGroupBox" name="grpDSiBattery">
|
||||
<property name="title">
|
||||
<string>DSi Battery</string>
|
||||
@ -219,6 +189,49 @@
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QGroupBox" name="grpDSBattery">
|
||||
<property name="title">
|
||||
<string>DS Battery</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_2">
|
||||
<item row="1" column="1">
|
||||
<widget class="QRadioButton" name="rbDSBatteryLow">
|
||||
<property name="text">
|
||||
<string>Low</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0" rowspan="2">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Battery Level</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QRadioButton" name="rbDSBatteryOkay">
|
||||
<property name="text">
|
||||
<string>Okay</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="lblInstanceNum">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Configuring settings for instance X</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources>
|
||||
|
@ -75,12 +75,12 @@ ROMInfoDialog::ROMInfoDialog(QWidget* parent) : QDialog(parent), ui(new Ui::ROMI
|
||||
|
||||
ui->iconTitle->setText(QString::fromUtf16(NDSCart::Banner.EnglishTitle));
|
||||
|
||||
ui->japaneseTitle->setText(QString::fromUtf16(NDSCart::Banner.JapaneseTitle, 128));
|
||||
ui->englishTitle->setText(QString::fromUtf16(NDSCart::Banner.EnglishTitle, 128));
|
||||
ui->frenchTitle->setText(QString::fromUtf16(NDSCart::Banner.FrenchTitle, 128));
|
||||
ui->germanTitle->setText(QString::fromUtf16(NDSCart::Banner.GermanTitle, 128));
|
||||
ui->italianTitle->setText(QString::fromUtf16(NDSCart::Banner.ItalianTitle, 128));
|
||||
ui->spanishTitle->setText(QString::fromUtf16(NDSCart::Banner.SpanishTitle, 128));
|
||||
ui->japaneseTitle->setText(QString::fromUtf16(NDSCart::Banner.JapaneseTitle));
|
||||
ui->englishTitle->setText(QString::fromUtf16(NDSCart::Banner.EnglishTitle));
|
||||
ui->frenchTitle->setText(QString::fromUtf16(NDSCart::Banner.FrenchTitle));
|
||||
ui->germanTitle->setText(QString::fromUtf16(NDSCart::Banner.GermanTitle));
|
||||
ui->italianTitle->setText(QString::fromUtf16(NDSCart::Banner.ItalianTitle));
|
||||
ui->spanishTitle->setText(QString::fromUtf16(NDSCart::Banner.SpanishTitle));
|
||||
|
||||
if (NDSCart::Banner.Version > 1)
|
||||
ui->chineseTitle->setText(QString::fromUtf16(NDSCart::Banner.ChineseTitle));
|
||||
|
@ -6,8 +6,8 @@
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>427</width>
|
||||
<height>434</height>
|
||||
<width>559</width>
|
||||
<height>532</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
@ -22,12 +22,6 @@
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="2" column="0">
|
||||
<widget class="QGroupBox" name="titles">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="title">
|
||||
<string>Titles</string>
|
||||
</property>
|
||||
@ -350,12 +344,6 @@
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="QGroupBox" name="filesystem">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="title">
|
||||
<string>Filesystem</string>
|
||||
</property>
|
||||
@ -441,12 +429,6 @@
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QGroupBox" name="generalInfo">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="title">
|
||||
<string>General info</string>
|
||||
</property>
|
||||
@ -668,7 +650,7 @@
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="3">
|
||||
<item row="0" column="2">
|
||||
<widget class="QGroupBox" name="dsiIconBox">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Minimum" vsizetype="Minimum">
|
||||
@ -742,43 +724,11 @@
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>55</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item row="0" column="4">
|
||||
<item row="0" column="3">
|
||||
<spacer name="horizontalSpacer_3">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item row="0" column="2">
|
||||
<spacer name="horizontalSpacer_2">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
@ -788,6 +738,13 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
|
@ -326,6 +326,7 @@ bool LoadState(std::string filename)
|
||||
|
||||
std::string savefile = filename.substr(LastSep(filename)+1);
|
||||
savefile = GetAssetPath(false, Config::SaveFilePath, ".sav", savefile);
|
||||
savefile += Platform::InstanceFileSuffix();
|
||||
NDSSave->SetPath(savefile, true);
|
||||
}
|
||||
|
||||
@ -350,6 +351,7 @@ bool SaveState(std::string filename)
|
||||
{
|
||||
std::string savefile = filename.substr(LastSep(filename)+1);
|
||||
savefile = GetAssetPath(false, Config::SaveFilePath, ".sav", savefile);
|
||||
savefile += Platform::InstanceFileSuffix();
|
||||
NDSSave->SetPath(savefile, false);
|
||||
}
|
||||
|
||||
@ -432,6 +434,7 @@ void Reset()
|
||||
{
|
||||
std::string oldsave = NDSSave->GetPath();
|
||||
std::string newsave = GetAssetPath(false, Config::SaveFilePath, ".sav");
|
||||
newsave += Platform::InstanceFileSuffix();
|
||||
if (oldsave != newsave)
|
||||
NDSSave->SetPath(newsave, false);
|
||||
}
|
||||
@ -440,6 +443,7 @@ void Reset()
|
||||
{
|
||||
std::string oldsave = GBASave->GetPath();
|
||||
std::string newsave = GetAssetPath(true, Config::SaveFilePath, ".sav");
|
||||
newsave += Platform::InstanceFileSuffix();
|
||||
if (oldsave != newsave)
|
||||
GBASave->SetPath(newsave, false);
|
||||
}
|
||||
@ -562,7 +566,11 @@ bool LoadROM(QStringList filepath, bool reset)
|
||||
u8* savedata = nullptr;
|
||||
|
||||
std::string savname = GetAssetPath(false, Config::SaveFilePath, ".sav");
|
||||
std::string origsav = savname;
|
||||
savname += Platform::InstanceFileSuffix();
|
||||
|
||||
FILE* sav = Platform::OpenFile(savname, "rb", true);
|
||||
if (!sav) sav = Platform::OpenFile(origsav, "rb", true);
|
||||
if (sav)
|
||||
{
|
||||
fseek(sav, 0, SEEK_END);
|
||||
@ -711,7 +719,11 @@ bool LoadGBAROM(QStringList filepath)
|
||||
u8* savedata = nullptr;
|
||||
|
||||
std::string savname = GetAssetPath(true, Config::SaveFilePath, ".sav");
|
||||
std::string origsav = savname;
|
||||
savname += Platform::InstanceFileSuffix();
|
||||
|
||||
FILE* sav = Platform::OpenFile(savname, "rb", true);
|
||||
if (!sav) sav = Platform::OpenFile(origsav, "rb", true);
|
||||
if (sav)
|
||||
{
|
||||
fseek(sav, 0, SEEK_END);
|
||||
|
@ -31,7 +31,7 @@
|
||||
#include "ui_TitleImportDialog.h"
|
||||
|
||||
|
||||
FILE* TitleManagerDialog::curNAND = nullptr;
|
||||
bool TitleManagerDialog::NANDInited = false;
|
||||
TitleManagerDialog* TitleManagerDialog::currentDlg = nullptr;
|
||||
|
||||
extern std::string EmuDirectory;
|
||||
@ -136,6 +136,8 @@ void TitleManagerDialog::createTitleItem(u32 category, u32 titleid)
|
||||
|
||||
bool TitleManagerDialog::openNAND()
|
||||
{
|
||||
NANDInited = false;
|
||||
|
||||
FILE* bios7i = Platform::OpenLocalFile(Config::DSiBIOS7Path, "rb");
|
||||
if (!bios7i)
|
||||
return false;
|
||||
@ -145,28 +147,21 @@ bool TitleManagerDialog::openNAND()
|
||||
fread(es_keyY, 16, 1, bios7i);
|
||||
fclose(bios7i);
|
||||
|
||||
curNAND = Platform::OpenLocalFile(Config::DSiNANDPath, "r+b");
|
||||
if (!curNAND)
|
||||
return false;
|
||||
|
||||
if (!DSi_NAND::Init(curNAND, es_keyY))
|
||||
if (!DSi_NAND::Init(es_keyY))
|
||||
{
|
||||
fclose(curNAND);
|
||||
curNAND = nullptr;
|
||||
return false;
|
||||
}
|
||||
|
||||
NANDInited = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
void TitleManagerDialog::closeNAND()
|
||||
{
|
||||
if (curNAND)
|
||||
if (NANDInited)
|
||||
{
|
||||
DSi_NAND::DeInit();
|
||||
|
||||
fclose(curNAND);
|
||||
curNAND = nullptr;
|
||||
NANDInited = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -45,7 +45,7 @@ public:
|
||||
explicit TitleManagerDialog(QWidget* parent);
|
||||
~TitleManagerDialog();
|
||||
|
||||
static FILE* curNAND;
|
||||
static bool NANDInited;
|
||||
static bool openNAND();
|
||||
static void closeNAND();
|
||||
|
||||
|
@ -50,12 +50,12 @@ WifiSettingsDialog::WifiSettingsDialog(QWidget* parent) : QDialog(parent), ui(ne
|
||||
ui->setupUi(this);
|
||||
setAttribute(Qt::WA_DeleteOnClose);
|
||||
|
||||
LAN_Socket::Init();
|
||||
haspcap = LAN_PCap::Init(false);
|
||||
|
||||
ui->rbDirectMode->setText("Direct mode (requires " PCAP_NAME " and ethernet connection)");
|
||||
|
||||
ui->cbBindAnyAddr->setChecked(Config::SocketBindAnyAddr);
|
||||
ui->lblAdapterMAC->setText("(none)");
|
||||
ui->lblAdapterIP->setText("(none)");
|
||||
|
||||
int sel = 0;
|
||||
for (int i = 0; i < LAN_PCap::NumAdapters; i++)
|
||||
@ -88,7 +88,6 @@ void WifiSettingsDialog::done(int r)
|
||||
|
||||
if (r == QDialog::Accepted)
|
||||
{
|
||||
Config::SocketBindAnyAddr = ui->cbBindAnyAddr->isChecked();
|
||||
Config::DirectLAN = ui->rbDirectMode->isChecked();
|
||||
|
||||
int sel = ui->cbxDirectAdapter->currentIndex();
|
||||
|
@ -7,7 +7,7 @@
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>572</width>
|
||||
<height>273</height>
|
||||
<height>217</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
@ -26,92 +26,10 @@
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox">
|
||||
<property name="title">
|
||||
<string>Local</string>
|
||||
<string>Network mode</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="0" column="0">
|
||||
<widget class="QCheckBox" name="cbBindAnyAddr">
|
||||
<property name="whatsThis">
|
||||
<string><html><head/><body><p>Enabling this allows (theoretically) playing local multiplayer games over a local network. It may or may not help make for a better connection in general.</p></body></html></string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Bind socket to any address</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox_2">
|
||||
<property name="title">
|
||||
<string>Online</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_2">
|
||||
<item row="3" column="0" rowspan="3" colspan="2">
|
||||
<widget class="QGroupBox" name="groupBox_3">
|
||||
<property name="title">
|
||||
<string>Direct mode settings</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_3">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Network adapter:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QComboBox" name="cbxDirectAdapter">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>300</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="whatsThis">
|
||||
<string><html><head/><body><p>Selects the network adapter through which to route network traffic under direct mode.</p></body></html></string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string>MAC address:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QLabel" name="lblAdapterMAC">
|
||||
<property name="text">
|
||||
<string>[PLACEHOLDER]</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="label_3">
|
||||
<property name="text">
|
||||
<string>IP address:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QLabel" name="lblAdapterIP">
|
||||
<property name="text">
|
||||
<string>[PLACEHOLDER]</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QRadioButton" name="rbIndirectMode">
|
||||
<property name="whatsThis">
|
||||
<string><html><head/><body><p>Indirect mode uses libslirp. It requires no extra setup and is easy to use.</p></body></html></string>
|
||||
@ -121,7 +39,7 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<item row="1" column="0">
|
||||
<widget class="QRadioButton" name="rbDirectMode">
|
||||
<property name="whatsThis">
|
||||
<string><html><head/><body><p>Direct mode directly routes network traffic to the host network. It is the most reliable, but requires an ethernet connection.</p><p><br/></p><p>Non-direct mode uses a layer of emulation to get around this, but is more prone to problems.</p></body></html></string>
|
||||
@ -134,6 +52,69 @@
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox_3">
|
||||
<property name="title">
|
||||
<string>Direct mode settings</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_3">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Network adapter:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QComboBox" name="cbxDirectAdapter">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>300</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="whatsThis">
|
||||
<string><html><head/><body><p>Selects the network adapter through which to route network traffic under direct mode.</p></body></html></string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string>MAC address:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QLabel" name="lblAdapterMAC">
|
||||
<property name="text">
|
||||
<string>[PLACEHOLDER]</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="label_3">
|
||||
<property name="text">
|
||||
<string>IP address:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QLabel" name="lblAdapterIP">
|
||||
<property name="text">
|
||||
<string>[PLACEHOLDER]</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QDialogButtonBox" name="buttonBox">
|
||||
<property name="orientation">
|
||||
|
@ -25,6 +25,7 @@
|
||||
#include <string>
|
||||
#include <algorithm>
|
||||
|
||||
#include <QProcess>
|
||||
#include <QApplication>
|
||||
#include <QMessageBox>
|
||||
#include <QMenuBar>
|
||||
@ -57,9 +58,11 @@
|
||||
#include "EmuSettingsDialog.h"
|
||||
#include "InputConfig/InputConfigDialog.h"
|
||||
#include "VideoSettingsDialog.h"
|
||||
#include "CameraSettingsDialog.h"
|
||||
#include "AudioSettingsDialog.h"
|
||||
#include "FirmwareSettingsDialog.h"
|
||||
#include "PathSettingsDialog.h"
|
||||
#include "MPSettingsDialog.h"
|
||||
#include "WifiSettingsDialog.h"
|
||||
#include "InterfaceSettingsDialog.h"
|
||||
#include "ROMInfoDialog.h"
|
||||
@ -80,6 +83,7 @@
|
||||
#include "SPU.h"
|
||||
#include "Wifi.h"
|
||||
#include "Platform.h"
|
||||
#include "LocalMP.h"
|
||||
#include "Config.h"
|
||||
|
||||
#include "Savestate.h"
|
||||
@ -88,6 +92,7 @@
|
||||
|
||||
#include "ROMManager.h"
|
||||
#include "ArchiveUtil.h"
|
||||
#include "CameraManager.h"
|
||||
|
||||
// TODO: uniform variable spelling
|
||||
|
||||
@ -104,6 +109,7 @@ bool videoSettingsDirty;
|
||||
|
||||
SDL_AudioDeviceID audioDevice;
|
||||
int audioFreq;
|
||||
bool audioMuted;
|
||||
SDL_cond* audioSync;
|
||||
SDL_mutex* audioSyncLock;
|
||||
|
||||
@ -114,10 +120,13 @@ u32 micExtBufferWritePos;
|
||||
u32 micWavLength;
|
||||
s16* micWavBuffer;
|
||||
|
||||
CameraManager* camManager[2];
|
||||
bool camStarted[2];
|
||||
|
||||
const struct { int id; float ratio; const char* label; } aspectRatios[] =
|
||||
{
|
||||
{ 0, 1, "4:3 (native)" },
|
||||
{ 4, (16.f / 10) / (4.f / 3), "16:10 (3DS)"},
|
||||
{ 4, (5.f / 3) / (4.f / 3), "5:3 (3DS)"},
|
||||
{ 1, (16.f / 9) / (4.f / 3), "16:9" },
|
||||
{ 2, (21.f / 9) / (4.f / 3), "21:9" },
|
||||
{ 3, 0, "window" }
|
||||
@ -126,6 +135,7 @@ const struct { int id; float ratio; const char* label; } aspectRatios[] =
|
||||
void micCallback(void* data, Uint8* stream, int len);
|
||||
|
||||
|
||||
|
||||
void audioCallback(void* data, Uint8* stream, int len)
|
||||
{
|
||||
len /= (sizeof(s16) * 2);
|
||||
@ -141,7 +151,7 @@ void audioCallback(void* data, Uint8* stream, int len)
|
||||
SDL_CondSignal(audioSync);
|
||||
SDL_UnlockMutex(audioSyncLock);
|
||||
|
||||
if (num_in < 1)
|
||||
if ((num_in < 1) || audioMuted)
|
||||
{
|
||||
memset(stream, 0, len*sizeof(s16)*2);
|
||||
return;
|
||||
@ -161,6 +171,23 @@ void audioCallback(void* data, Uint8* stream, int len)
|
||||
Frontend::AudioOut_Resample(buf_in, num_in, (s16*)stream, len, Config::AudioVolume);
|
||||
}
|
||||
|
||||
void audioMute()
|
||||
{
|
||||
int inst = Platform::InstanceID();
|
||||
audioMuted = false;
|
||||
|
||||
switch (Config::MPAudioMode)
|
||||
{
|
||||
case 1: // only instance 1
|
||||
if (inst > 0) audioMuted = true;
|
||||
break;
|
||||
|
||||
case 2: // only currently focused instance
|
||||
if (!mainWindow->isActiveWindow()) audioMuted = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void micOpen()
|
||||
{
|
||||
@ -700,7 +727,11 @@ void EmuThread::run()
|
||||
if (winUpdateFreq < 1)
|
||||
winUpdateFreq = 1;
|
||||
|
||||
sprintf(melontitle, "[%d/%.0f] melonDS " MELONDS_VERSION, fps, fpstarget);
|
||||
int inst = Platform::InstanceID();
|
||||
if (inst == 0)
|
||||
sprintf(melontitle, "[%d/%.0f] melonDS " MELONDS_VERSION, fps, fpstarget);
|
||||
else
|
||||
sprintf(melontitle, "[%d/%.0f] melonDS (%d)", fps, fpstarget, inst+1);
|
||||
changeWindowTitle(melontitle);
|
||||
}
|
||||
}
|
||||
@ -715,7 +746,11 @@ void EmuThread::run()
|
||||
|
||||
EmuStatus = EmuRunning;
|
||||
|
||||
sprintf(melontitle, "melonDS " MELONDS_VERSION);
|
||||
int inst = Platform::InstanceID();
|
||||
if (inst == 0)
|
||||
sprintf(melontitle, "melonDS " MELONDS_VERSION);
|
||||
else
|
||||
sprintf(melontitle, "melonDS (%d)", inst+1);
|
||||
changeWindowTitle(melontitle);
|
||||
|
||||
SDL_Delay(75);
|
||||
@ -942,7 +977,7 @@ void ScreenHandler::screenSetupLayout(int w, int h)
|
||||
QSize ScreenHandler::screenGetMinSize(int factor = 1)
|
||||
{
|
||||
bool isHori = (Config::ScreenRotation == 1 || Config::ScreenRotation == 3);
|
||||
int gap = Config::ScreenGap;
|
||||
int gap = Config::ScreenGap * factor;
|
||||
|
||||
int w = 256 * factor;
|
||||
int h = 192 * factor;
|
||||
@ -976,9 +1011,9 @@ QSize ScreenHandler::screenGetMinSize(int factor = 1)
|
||||
else // hybrid
|
||||
{
|
||||
if (isHori)
|
||||
return QSize(h+gap+h, 3*w +(4*gap) / 3);
|
||||
return QSize(h+gap+h, 3*w + (int)ceil((4*gap) / 3.0));
|
||||
else
|
||||
return QSize(3*w +(4*gap) / 3, h+gap+h);
|
||||
return QSize(3*w + (int)ceil((4*gap) / 3.0), h+gap+h);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1406,6 +1441,9 @@ MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent)
|
||||
setWindowTitle("melonDS " MELONDS_VERSION);
|
||||
setAttribute(Qt::WA_DeleteOnClose);
|
||||
setAcceptDrops(true);
|
||||
setFocusPolicy(Qt::ClickFocus);
|
||||
|
||||
int inst = Platform::InstanceID();
|
||||
|
||||
QMenuBar* menubar = new QMenuBar();
|
||||
{
|
||||
@ -1538,19 +1576,30 @@ MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent)
|
||||
actEnableCheats->setCheckable(true);
|
||||
connect(actEnableCheats, &QAction::triggered, this, &MainWindow::onEnableCheats);
|
||||
|
||||
actSetupCheats = menu->addAction("Setup cheat codes");
|
||||
actSetupCheats->setMenuRole(QAction::NoRole);
|
||||
connect(actSetupCheats, &QAction::triggered, this, &MainWindow::onSetupCheats);
|
||||
//if (inst == 0)
|
||||
{
|
||||
actSetupCheats = menu->addAction("Setup cheat codes");
|
||||
actSetupCheats->setMenuRole(QAction::NoRole);
|
||||
connect(actSetupCheats, &QAction::triggered, this, &MainWindow::onSetupCheats);
|
||||
|
||||
menu->addSeparator();
|
||||
actROMInfo = menu->addAction("ROM info");
|
||||
connect(actROMInfo, &QAction::triggered, this, &MainWindow::onROMInfo);
|
||||
menu->addSeparator();
|
||||
actROMInfo = menu->addAction("ROM info");
|
||||
connect(actROMInfo, &QAction::triggered, this, &MainWindow::onROMInfo);
|
||||
|
||||
actRAMInfo = menu->addAction("RAM search");
|
||||
connect(actRAMInfo, &QAction::triggered, this, &MainWindow::onRAMInfo);
|
||||
actRAMInfo = menu->addAction("RAM search");
|
||||
connect(actRAMInfo, &QAction::triggered, this, &MainWindow::onRAMInfo);
|
||||
|
||||
actTitleManager = menu->addAction("Manage DSi titles");
|
||||
connect(actTitleManager, &QAction::triggered, this, &MainWindow::onOpenTitleManager);
|
||||
actTitleManager = menu->addAction("Manage DSi titles");
|
||||
connect(actTitleManager, &QAction::triggered, this, &MainWindow::onOpenTitleManager);
|
||||
}
|
||||
|
||||
{
|
||||
menu->addSeparator();
|
||||
QMenu* submenu = menu->addMenu("Multiplayer");
|
||||
|
||||
actMPNewInstance = submenu->addAction("Launch new instance");
|
||||
connect(actMPNewInstance, &QAction::triggered, this, &MainWindow::onMPNewInstance);
|
||||
}
|
||||
}
|
||||
{
|
||||
QMenu* menu = menubar->addMenu("Config");
|
||||
@ -1559,7 +1608,7 @@ MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent)
|
||||
connect(actEmuSettings, &QAction::triggered, this, &MainWindow::onOpenEmuSettings);
|
||||
|
||||
#ifdef __APPLE__
|
||||
QAction* actPreferences = menu->addAction("Preferences...");
|
||||
actPreferences = menu->addAction("Preferences...");
|
||||
connect(actPreferences, &QAction::triggered, this, &MainWindow::onOpenEmuSettings);
|
||||
actPreferences->setMenuRole(QAction::PreferencesRole);
|
||||
#endif
|
||||
@ -1570,18 +1619,24 @@ MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent)
|
||||
actVideoSettings = menu->addAction("Video settings");
|
||||
connect(actVideoSettings, &QAction::triggered, this, &MainWindow::onOpenVideoSettings);
|
||||
|
||||
actCameraSettings = menu->addAction("Camera settings");
|
||||
connect(actCameraSettings, &QAction::triggered, this, &MainWindow::onOpenCameraSettings);
|
||||
|
||||
actAudioSettings = menu->addAction("Audio settings");
|
||||
connect(actAudioSettings, &QAction::triggered, this, &MainWindow::onOpenAudioSettings);
|
||||
|
||||
actMPSettings = menu->addAction("Multiplayer settings");
|
||||
connect(actMPSettings, &QAction::triggered, this, &MainWindow::onOpenMPSettings);
|
||||
|
||||
actWifiSettings = menu->addAction("Wifi settings");
|
||||
connect(actWifiSettings, &QAction::triggered, this, &MainWindow::onOpenWifiSettings);
|
||||
|
||||
actInterfaceSettings = menu->addAction("Interface settings");
|
||||
connect(actInterfaceSettings, &QAction::triggered, this, &MainWindow::onOpenInterfaceSettings);
|
||||
|
||||
actFirmwareSettings = menu->addAction("Firmware settings");
|
||||
connect(actFirmwareSettings, &QAction::triggered, this, &MainWindow::onOpenFirmwareSettings);
|
||||
|
||||
actInterfaceSettings = menu->addAction("Interface settings");
|
||||
connect(actInterfaceSettings, &QAction::triggered, this, &MainWindow::onOpenInterfaceSettings);
|
||||
|
||||
actPathSettings = menu->addAction("Path settings");
|
||||
connect(actPathSettings, &QAction::triggered, this, &MainWindow::onOpenPathSettings);
|
||||
|
||||
@ -1737,6 +1792,9 @@ MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent)
|
||||
|
||||
resize(Config::WindowWidth, Config::WindowHeight);
|
||||
|
||||
if (Config::FirmwareUsername == "Arisotura")
|
||||
actMPNewInstance->setText("Fart");
|
||||
|
||||
#ifdef Q_OS_MAC
|
||||
QPoint screenCenter = screen()->availableGeometry().center();
|
||||
QRect frameGeo = frameGeometry();
|
||||
@ -1816,6 +1874,19 @@ MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent)
|
||||
|
||||
actLimitFramerate->setChecked(Config::LimitFPS);
|
||||
actAudioSync->setChecked(Config::AudioSync);
|
||||
|
||||
if (inst > 0)
|
||||
{
|
||||
actEmuSettings->setEnabled(false);
|
||||
actVideoSettings->setEnabled(false);
|
||||
actMPSettings->setEnabled(false);
|
||||
actWifiSettings->setEnabled(false);
|
||||
actInterfaceSettings->setEnabled(false);
|
||||
|
||||
#ifdef __APPLE__
|
||||
actPreferences->setEnabled(false);
|
||||
#endif // __APPLE__
|
||||
}
|
||||
}
|
||||
|
||||
MainWindow::~MainWindow()
|
||||
@ -2005,6 +2076,16 @@ void MainWindow::dropEvent(QDropEvent* event)
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::focusInEvent(QFocusEvent* event)
|
||||
{
|
||||
audioMute();
|
||||
}
|
||||
|
||||
void MainWindow::focusOutEvent(QFocusEvent* event)
|
||||
{
|
||||
audioMute();
|
||||
}
|
||||
|
||||
void MainWindow::onAppStateChanged(Qt::ApplicationState state)
|
||||
{
|
||||
if (state == Qt::ApplicationInactive)
|
||||
@ -2677,6 +2758,23 @@ void MainWindow::onOpenTitleManager()
|
||||
TitleManagerDialog* dlg = TitleManagerDialog::openDlg(this);
|
||||
}
|
||||
|
||||
void MainWindow::onMPNewInstance()
|
||||
{
|
||||
//QProcess::startDetached(QApplication::applicationFilePath());
|
||||
QProcess newinst;
|
||||
newinst.setProgram(QApplication::applicationFilePath());
|
||||
newinst.setArguments(QApplication::arguments().mid(1, QApplication::arguments().length()-1));
|
||||
|
||||
#ifdef __WIN32__
|
||||
newinst.setCreateProcessArgumentsModifier([] (QProcess::CreateProcessArguments *args)
|
||||
{
|
||||
args->flags |= CREATE_NEW_CONSOLE;
|
||||
});
|
||||
#endif
|
||||
|
||||
newinst.startDetached();
|
||||
}
|
||||
|
||||
void MainWindow::onOpenEmuSettings()
|
||||
{
|
||||
emuThread->emuPause();
|
||||
@ -2737,6 +2835,27 @@ void MainWindow::onOpenVideoSettings()
|
||||
connect(dlg, &VideoSettingsDialog::updateVideoSettings, this, &MainWindow::onUpdateVideoSettings);
|
||||
}
|
||||
|
||||
void MainWindow::onOpenCameraSettings()
|
||||
{
|
||||
emuThread->emuPause();
|
||||
|
||||
camStarted[0] = camManager[0]->isStarted();
|
||||
camStarted[1] = camManager[1]->isStarted();
|
||||
if (camStarted[0]) camManager[0]->stop();
|
||||
if (camStarted[1]) camManager[1]->stop();
|
||||
|
||||
CameraSettingsDialog* dlg = CameraSettingsDialog::openDlg(this);
|
||||
connect(dlg, &CameraSettingsDialog::finished, this, &MainWindow::onCameraSettingsFinished);
|
||||
}
|
||||
|
||||
void MainWindow::onCameraSettingsFinished(int res)
|
||||
{
|
||||
if (camStarted[0]) camManager[0]->start();
|
||||
if (camStarted[1]) camManager[1]->start();
|
||||
|
||||
emuThread->emuUnpause();
|
||||
}
|
||||
|
||||
void MainWindow::onOpenAudioSettings()
|
||||
{
|
||||
AudioSettingsDialog* dlg = AudioSettingsDialog::openDlg(this);
|
||||
@ -2811,6 +2930,22 @@ void MainWindow::onAudioSettingsFinished(int res)
|
||||
micOpen();
|
||||
}
|
||||
|
||||
void MainWindow::onOpenMPSettings()
|
||||
{
|
||||
emuThread->emuPause();
|
||||
|
||||
MPSettingsDialog* dlg = MPSettingsDialog::openDlg(this);
|
||||
connect(dlg, &MPSettingsDialog::finished, this, &MainWindow::onMPSettingsFinished);
|
||||
}
|
||||
|
||||
void MainWindow::onMPSettingsFinished(int res)
|
||||
{
|
||||
audioMute();
|
||||
LocalMP::SetRecvTimeout(Config::MPRecvTimeout);
|
||||
|
||||
emuThread->emuUnpause();
|
||||
}
|
||||
|
||||
void MainWindow::onOpenWifiSettings()
|
||||
{
|
||||
emuThread->emuPause();
|
||||
@ -2821,12 +2956,6 @@ void MainWindow::onOpenWifiSettings()
|
||||
|
||||
void MainWindow::onWifiSettingsFinished(int res)
|
||||
{
|
||||
if (Wifi::MPInited)
|
||||
{
|
||||
Platform::MP_DeInit();
|
||||
Platform::MP_Init();
|
||||
}
|
||||
|
||||
Platform::LAN_DeInit();
|
||||
Platform::LAN_Init();
|
||||
|
||||
@ -3081,7 +3210,7 @@ bool MelonApplication::event(QEvent *event)
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
srand(time(NULL));
|
||||
srand(time(nullptr));
|
||||
|
||||
qputenv("QT_SCALE_FACTOR", "1");
|
||||
|
||||
@ -3139,6 +3268,7 @@ int main(int argc, char** argv)
|
||||
SANITIZE(Config::ScreenAspectBot, 0, 4);
|
||||
#undef SANITIZE
|
||||
|
||||
audioMuted = false;
|
||||
audioSync = SDL_CreateCond();
|
||||
audioSyncLock = SDL_CreateMutex();
|
||||
|
||||
@ -3164,11 +3294,17 @@ int main(int argc, char** argv)
|
||||
|
||||
micDevice = 0;
|
||||
|
||||
|
||||
memset(micExtBuffer, 0, sizeof(micExtBuffer));
|
||||
micExtBufferWritePos = 0;
|
||||
micWavBuffer = nullptr;
|
||||
|
||||
camStarted[0] = false;
|
||||
camStarted[1] = false;
|
||||
camManager[0] = new CameraManager(0, 640, 480, true);
|
||||
camManager[1] = new CameraManager(1, 640, 480, true);
|
||||
camManager[0]->setXFlip(Config::Camera[0].XFlip);
|
||||
camManager[1]->setXFlip(Config::Camera[1].XFlip);
|
||||
|
||||
ROMManager::EnableCheats(Config::EnableCheats != 0);
|
||||
|
||||
Frontend::Init_Audio(audioFreq);
|
||||
@ -3192,6 +3328,8 @@ int main(int argc, char** argv)
|
||||
emuThread->start();
|
||||
emuThread->emuPause();
|
||||
|
||||
audioMute();
|
||||
|
||||
QObject::connect(&melon, &QApplication::applicationStateChanged, mainWindow, &MainWindow::onAppStateChanged);
|
||||
|
||||
if (argc > 1)
|
||||
@ -3219,6 +3357,9 @@ int main(int argc, char** argv)
|
||||
|
||||
if (micWavBuffer) delete[] micWavBuffer;
|
||||
|
||||
delete camManager[0];
|
||||
delete camManager[1];
|
||||
|
||||
Config::Save();
|
||||
|
||||
SDL_Quit();
|
||||
@ -3249,7 +3390,8 @@ int CALLBACK WinMain(HINSTANCE hinst, HINSTANCE hprev, LPSTR cmdline, int cmdsho
|
||||
|
||||
if (argv_w) LocalFree(argv_w);
|
||||
|
||||
/*if (AttachConsole(ATTACH_PARENT_PROCESS))
|
||||
//if (AttachConsole(ATTACH_PARENT_PROCESS))
|
||||
/*if (AllocConsole())
|
||||
{
|
||||
freopen("CONOUT$", "w", stdout);
|
||||
freopen("CONOUT$", "w", stderr);
|
||||
|
@ -254,6 +254,9 @@ protected:
|
||||
void dragEnterEvent(QDragEnterEvent* event) override;
|
||||
void dropEvent(QDropEvent* event) override;
|
||||
|
||||
void focusInEvent(QFocusEvent* event) override;
|
||||
void focusOutEvent(QFocusEvent* event) override;
|
||||
|
||||
signals:
|
||||
void screenLayoutChange();
|
||||
|
||||
@ -283,6 +286,7 @@ private slots:
|
||||
void onROMInfo();
|
||||
void onRAMInfo();
|
||||
void onOpenTitleManager();
|
||||
void onMPNewInstance();
|
||||
|
||||
void onOpenEmuSettings();
|
||||
void onEmuSettingsDialogFinished(int res);
|
||||
@ -290,14 +294,18 @@ private slots:
|
||||
void onOpenInputConfig();
|
||||
void onInputConfigFinished(int res);
|
||||
void onOpenVideoSettings();
|
||||
void onOpenCameraSettings();
|
||||
void onCameraSettingsFinished(int res);
|
||||
void onOpenAudioSettings();
|
||||
void onOpenFirmwareSettings();
|
||||
void onOpenPathSettings();
|
||||
void onUpdateAudioSettings();
|
||||
void onAudioSettingsFinished(int res);
|
||||
void onOpenMPSettings();
|
||||
void onMPSettingsFinished(int res);
|
||||
void onOpenWifiSettings();
|
||||
void onWifiSettingsFinished(int res);
|
||||
void onOpenFirmwareSettings();
|
||||
void onFirmwareSettingsFinished(int res);
|
||||
void onOpenPathSettings();
|
||||
void onPathSettingsFinished(int res);
|
||||
void onOpenInterfaceSettings();
|
||||
void onInterfaceSettingsFinished(int res);
|
||||
@ -374,12 +382,18 @@ public:
|
||||
QAction* actROMInfo;
|
||||
QAction* actRAMInfo;
|
||||
QAction* actTitleManager;
|
||||
QAction* actMPNewInstance;
|
||||
|
||||
QAction* actEmuSettings;
|
||||
#ifdef __APPLE__
|
||||
QAction* actPreferences;
|
||||
#endif
|
||||
QAction* actPowerManagement;
|
||||
QAction* actInputConfig;
|
||||
QAction* actVideoSettings;
|
||||
QAction* actCameraSettings;
|
||||
QAction* actAudioSettings;
|
||||
QAction* actMPSettings;
|
||||
QAction* actWifiSettings;
|
||||
QAction* actFirmwareSettings;
|
||||
QAction* actPathSettings;
|
||||
|
488
src/frontend/qt_sdl/sem_timedwait.cpp
Normal file
488
src/frontend/qt_sdl/sem_timedwait.cpp
Normal file
@ -0,0 +1,488 @@
|
||||
/*
|
||||
* s e m _ t i m e d w a i t
|
||||
*
|
||||
* Function:
|
||||
* Implements a version of sem_timedwait().
|
||||
*
|
||||
* Description:
|
||||
* Not all systems implement sem_timedwait(), which is a version of
|
||||
* sem_wait() with a timeout. Mac OS X is one example, at least up to
|
||||
* and including version 10.6 (Leopard). If such a function is needed,
|
||||
* this code provides a reasonable implementation, which I think is
|
||||
* compatible with the standard version, although possibly less
|
||||
* efficient. It works by creating a thread that interrupts a normal
|
||||
* sem_wait() call after the specified timeout.
|
||||
*
|
||||
* Call:
|
||||
*
|
||||
* The Linux man pages say:
|
||||
*
|
||||
* #include <semaphore.h>
|
||||
*
|
||||
* int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout);
|
||||
*
|
||||
* sem_timedwait() is the same as sem_wait(), except that abs_timeout
|
||||
* specifies a limit on the amount of time that the call should block if
|
||||
* the decrement cannot be immediately performed. The abs_timeout argument
|
||||
* points to a structure that specifies an absolute timeout in seconds and
|
||||
* nanoseconds since the Epoch (00:00:00, 1 January 1970). This structure
|
||||
* is defined as follows:
|
||||
*
|
||||
* struct timespec {
|
||||
* time_t tv_sec; Seconds
|
||||
* long tv_nsec; Nanoseconds [0 .. 999999999]
|
||||
* };
|
||||
*
|
||||
* If the timeout has already expired by the time of the call, and the
|
||||
* semaphore could not be locked immediately, then sem_timedwait() fails
|
||||
* with a timeout error (errno set to ETIMEDOUT).
|
||||
* If the operation can be performed immediately, then sem_timedwait()
|
||||
* never fails with a timeout error, regardless of the value of abs_timeout.
|
||||
* Furthermore, the validity of abs_timeout is not checked in this case.
|
||||
*
|
||||
* Limitations:
|
||||
*
|
||||
* The mechanism used involves sending a SIGUSR2 signal to the thread
|
||||
* calling sem_timedwait(). The handler for this signal is set to a null
|
||||
* routine which does nothing, and with any flags for the signal
|
||||
* (eg SA_RESTART) cleared. Note that this effective disabling of the
|
||||
* SIGUSR2 signal is a side-effect of using this routine, and means it
|
||||
* may not be a completely transparent plug-in replacement for a
|
||||
* 'normal' sig_timedwait() call. Since OS X does not declare the
|
||||
* sem_timedwait() call in its standard include files, the relevant
|
||||
* declaration (shown above in the man pages extract) will probably have
|
||||
* to be added to any code that uses this.
|
||||
*
|
||||
* Compiling:
|
||||
* This compiles and runs cleanly on OS X (10.6) with gcc with the
|
||||
* -Wall -ansi -pedantic flags. On Linux, using -ansi causes a sweep of
|
||||
* compiler complaints about the timespec structure, but it compiles
|
||||
* and works fine with just -Wall -pedantic. (Since Linux provides
|
||||
* sem_timedwait() anyway, this really isn't needed on Linux.) However,
|
||||
* since Linux provides sem_timedwait anyway, the sem_timedwait()
|
||||
* code in this file is only compiled on OS X, and is a null on other
|
||||
* systems.
|
||||
*
|
||||
* Testing:
|
||||
* This file contains a test program that exercises the sem_timedwait
|
||||
* code. It is compiled if the pre-processor variable TEST is defined.
|
||||
* For more details, see the comments for the test routine at the end
|
||||
* of the file.
|
||||
*
|
||||
* Author: Keith Shortridge, AAO.
|
||||
*
|
||||
* History:
|
||||
* 8th Sep 2009. Original version. KS.
|
||||
* 24th Sep 2009. Added test that the calling thread still exists before
|
||||
* trying to set the timed-out flag. KS.
|
||||
* 2nd Oct 2009. No longer restores the original SIGUSR2 signal handler.
|
||||
* See comments in the body of the code for more details.
|
||||
* Prototypes for now discontinued internal routines removed.
|
||||
* 12th Aug 2010. Added the cleanup handler, so that this code no longer
|
||||
* leaks resources if the calling thread is cancelled. KS.
|
||||
* 21st Sep 2011. Added copyright notice below. Modified header comments
|
||||
* to describe the use of SIGUSR2 more accurately in the
|
||||
* light of the 2/10/09 change above. Now undefs DEBUG
|
||||
* before defining it, to avoid any possible clash. KS.
|
||||
* 14th Feb 2012. Tidied out a number of TABs that had got into the
|
||||
* code. KS.
|
||||
* 6th May 2013. Copyright notice modified to one based on the MIT licence,
|
||||
* which is more permissive than the previous notice. KS.
|
||||
*
|
||||
* Copyright (c) Australian Astronomical Observatory (AAO), (2013).
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a
|
||||
* copy of this software and associated documentation files (the "Software"),
|
||||
* to deal in the Software without restriction, including without limitation
|
||||
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
* and/or sell copies of the Software, and to permit persons to whom the
|
||||
* Software is furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
* IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#ifdef __APPLE__
|
||||
|
||||
#include <semaphore.h>
|
||||
#include <time.h>
|
||||
#include <sys/time.h>
|
||||
#include <pthread.h>
|
||||
#include <errno.h>
|
||||
#include <signal.h>
|
||||
#include <stdio.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/fcntl.h>
|
||||
#include <setjmp.h>
|
||||
|
||||
#include "sem_timedwait.h"
|
||||
|
||||
/* Some useful definitions - TRUE, FALSE, and DEBUG */
|
||||
|
||||
#undef TRUE
|
||||
#define TRUE 1
|
||||
#undef FALSE
|
||||
#define FALSE 0
|
||||
#undef DEBUG
|
||||
#define DEBUG printf
|
||||
|
||||
/* A structure of type timeoutDetails is passed to the thread used to
|
||||
* implement the timeout.
|
||||
*/
|
||||
|
||||
typedef struct {
|
||||
struct timespec delay; /* Specifies the delay, relative to now */
|
||||
pthread_t callingThread; /* The thread doing the sem_wait call */
|
||||
volatile short *timedOutShort; /* Address of a flag set to indicate that
|
||||
* the timeout was triggered. */
|
||||
} timeoutDetails;
|
||||
|
||||
/* A structure of type cleanupDetails is passed to the thread cleanup
|
||||
* routine which is called at the end of the routine or if the thread calling
|
||||
* it is cancelled.
|
||||
*/
|
||||
|
||||
typedef struct {
|
||||
pthread_t *threadIdAddr; /* Address of the variable that holds
|
||||
* the Id of the timeout thread. */
|
||||
struct sigaction *sigHandlerAddr; /* Address of the old signal action
|
||||
* handler. */
|
||||
volatile short *timedOutShort; /* Address of a flag set to indicate that
|
||||
* the timeout was triggered. */
|
||||
} cleanupDetails;
|
||||
|
||||
/* Forward declarations of internal routines */
|
||||
|
||||
static void* timeoutThreadMain (void* passedPtr);
|
||||
static int triggerSignal (int Signal, pthread_t Thread);
|
||||
static void ignoreSignal (int Signal);
|
||||
static void timeoutThreadCleanup (void* passedPtr);
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/*
|
||||
* s e m _ t i m e d w a i t
|
||||
*
|
||||
* This is the main code for the sem_timedwait() implementation.
|
||||
*/
|
||||
|
||||
int sem_timedwait (
|
||||
sem_t *sem,
|
||||
const struct timespec *abs_timeout)
|
||||
{
|
||||
int result = 0; /* Code returned by this routine 0 or -1 */
|
||||
|
||||
/* "Under no circumstances shall the function fail if the semaphore
|
||||
* can be locked immediately". So we try to get it quickly to see if we
|
||||
* can avoid all the timeout overheads.
|
||||
*/
|
||||
|
||||
if (sem_trywait(sem) == 0) {
|
||||
|
||||
/* Yes, got it immediately. */
|
||||
|
||||
result = 0;
|
||||
|
||||
} else {
|
||||
|
||||
/* No, we've got to do it with a sem_wait() call and a thread to run
|
||||
* the timeout. First, work out the time from now to the specified
|
||||
* timeout, which we will pass to the timeout thread in a way that can
|
||||
* be used to pass to nanosleep(). So we need this in seconds and
|
||||
* nanoseconds. Along the way, we check for an invalid passed time,
|
||||
* and for one that's already expired.
|
||||
*/
|
||||
|
||||
if ((abs_timeout->tv_nsec < 0) || (abs_timeout->tv_nsec > 1000000000)) {
|
||||
|
||||
/* Passed time is invalid */
|
||||
|
||||
result = -1;
|
||||
errno = EINVAL;
|
||||
|
||||
} else {
|
||||
|
||||
struct timeval currentTime; /* Time now */
|
||||
long secsToWait,nsecsToWait; /* Seconds and nsec to delay */
|
||||
gettimeofday (¤tTime,NULL);
|
||||
secsToWait = abs_timeout->tv_sec - currentTime.tv_sec;
|
||||
nsecsToWait = (abs_timeout->tv_nsec - (currentTime.tv_usec * 1000));
|
||||
while (nsecsToWait < 0) {
|
||||
nsecsToWait += 1000000000;
|
||||
secsToWait--;
|
||||
}
|
||||
if ((secsToWait < 0) || ((secsToWait == 0) && (nsecsToWait < 0))) {
|
||||
|
||||
/* Time has passed. Report an immediate timeout. */
|
||||
|
||||
result = -1;
|
||||
errno = ETIMEDOUT;
|
||||
|
||||
} else {
|
||||
|
||||
/* We're going to have to do a sem_wait() with a timeout thread.
|
||||
* The thread will wait the specified time, then will issue a
|
||||
* SIGUSR2 signal that will interrupt the sem_wait() call.
|
||||
* We pass the thread the id of the current thread, the delay,
|
||||
* and the address of a flag to set on a timeout, so we can
|
||||
* distinguish an interrupt caused by the timeout thread from
|
||||
* one caused by some other signal.
|
||||
*/
|
||||
|
||||
volatile short timedOut; /* Flag to set on timeout */
|
||||
timeoutDetails details; /* All the stuff the thread must know */
|
||||
struct sigaction oldSignalAction; /* Current signal setting */
|
||||
pthread_t timeoutThread; /* Id of timeout thread */
|
||||
cleanupDetails cleaningDetails; /* What the cleanup routine needs */
|
||||
int oldCancelState; /* Previous cancellation state */
|
||||
int ignoreCancelState; /* Used in call, but ignored */
|
||||
int createStatus; /* Status of pthread_create() call */
|
||||
|
||||
/* If the current thread is cancelled (and CML does do this)
|
||||
* we don't want to leave our timer thread running - if we've
|
||||
* started the thread we want to make sure we join it in order
|
||||
* to release its resources. So we set a cleanup handler to
|
||||
* do this. We pass it the address of the structure that will
|
||||
* hold all it needs to know. While we set all this up,
|
||||
* we prevent ourselves being cancelled, so all this data is
|
||||
* coherent.
|
||||
*/
|
||||
|
||||
pthread_setcancelstate (PTHREAD_CANCEL_DISABLE,&oldCancelState);
|
||||
timeoutThread = (pthread_t) 0;
|
||||
cleaningDetails.timedOutShort = &timedOut;
|
||||
cleaningDetails.threadIdAddr = &timeoutThread;
|
||||
cleaningDetails.sigHandlerAddr = &oldSignalAction;
|
||||
pthread_cleanup_push (timeoutThreadCleanup,&cleaningDetails);
|
||||
|
||||
/* Set up the details for the thread. Clear the timeout flag,
|
||||
* record the current SIGUSR2 action settings so we can restore
|
||||
* them later.
|
||||
*/
|
||||
|
||||
details.delay.tv_sec = secsToWait;
|
||||
details.delay.tv_nsec = nsecsToWait;
|
||||
details.callingThread = pthread_self();
|
||||
details.timedOutShort = &timedOut;
|
||||
timedOut = FALSE;
|
||||
sigaction (SIGUSR2,NULL,&oldSignalAction);
|
||||
|
||||
/* Start up the timeout thread. Once we've done that, we can
|
||||
* restore the previous cancellation state.
|
||||
*/
|
||||
|
||||
createStatus = pthread_create(&timeoutThread,NULL,
|
||||
timeoutThreadMain, (void*)&details);
|
||||
pthread_setcancelstate (oldCancelState,&ignoreCancelState);
|
||||
|
||||
if (createStatus < 0) {
|
||||
|
||||
/* Failed to create thread. errno will already be set properly */
|
||||
|
||||
result = -1;
|
||||
|
||||
} else {
|
||||
|
||||
/* Thread created OK. This is where we wait for the semaphore.
|
||||
*/
|
||||
|
||||
if (sem_wait(sem) == 0) {
|
||||
|
||||
/* Got the semaphore OK. We return zero, and all's well. */
|
||||
|
||||
result = 0;
|
||||
|
||||
} else {
|
||||
|
||||
/* If we got a -1 error from sem_wait(), it may be because
|
||||
* it was interrupted by a timeout, or failed for some
|
||||
* other reason. We check for the expected timeout
|
||||
* condition, which is an 'interrupted' status and the
|
||||
* timeout flag set by the timeout thread. We report that as
|
||||
* a timeout error. Anything else is some other error and
|
||||
* errno is already set properly.
|
||||
*/
|
||||
|
||||
result = -1;
|
||||
if (errno == EINTR) {
|
||||
if (timedOut) errno = ETIMEDOUT;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/* The cleanup routine - timeoutThreadCleanup() - packages up
|
||||
* any tidying up that is needed, including joining with the
|
||||
* timer thread. This will be called if the current thread is
|
||||
* cancelled, but we need it to happen anyway, so we set the
|
||||
* execute flag true here as we remove it from the list of
|
||||
* cleanup routines to be called. So normally, this line amounts
|
||||
* to calling timeoutThreadCleanup().
|
||||
*/
|
||||
|
||||
pthread_cleanup_pop (TRUE);
|
||||
}
|
||||
}
|
||||
}
|
||||
return (result);
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/*
|
||||
* t i m e o u t T h r e a d C l e a n u p
|
||||
*
|
||||
* This internal routine tidies up at the end of a sem_timedwait() call.
|
||||
* It is set as a cleanup routine for the current thread (not the timer
|
||||
* thread) so it is executed even if the thread is cancelled. This is
|
||||
* important, as we need to tidy up the timeout thread. If we took the
|
||||
* semaphore (in other words, if we didn't timeout) then the timer thread
|
||||
* will still be running, sitting in its nanosleep() call, and we need
|
||||
* to cancel it. If the timer thread did signal a timeout then it will
|
||||
* now be closing down. In either case, we need to join it (using a call
|
||||
* to pthread_join()) or its resources will never be released.
|
||||
* The single argument is a pointer to a cleanupDetails structure that has
|
||||
* all the routine needs to know.
|
||||
*/
|
||||
|
||||
static void timeoutThreadCleanup (void* passedPtr)
|
||||
{
|
||||
/* Get what we need from the structure we've been passed. */
|
||||
|
||||
cleanupDetails *detailsPtr = (cleanupDetails*) passedPtr;
|
||||
short timedOut = *(detailsPtr->timedOutShort);
|
||||
pthread_t timeoutThread = *(detailsPtr->threadIdAddr);
|
||||
|
||||
/* If we created the thread, stop it - doesn't matter if it's no longer
|
||||
* running, pthread_cancel can handle that. We make sure we wait for it
|
||||
* to complete, because it is this pthread_join() call that releases any
|
||||
* memory the thread may have allocated. Note that cancelling a thread is
|
||||
* generally not a good idea, because of the difficulty of cleaning up
|
||||
* after it, but this is a very simple thread that does nothing but call
|
||||
* nanosleep(), and that we can cancel quite happily.
|
||||
*/
|
||||
|
||||
if (!timedOut) pthread_cancel(timeoutThread);
|
||||
pthread_join(timeoutThread,NULL);
|
||||
|
||||
/* The code originally restored the old action handler, which generally
|
||||
* was the default handler that caused the task to exit. Just occasionally,
|
||||
* there seem to be cases where the signal is still queued and ready to
|
||||
* trigger even though the thread that presumably sent it off just before
|
||||
* it was cancelled has finished. I had thought that once we'd joined
|
||||
* that thread, we could be sure of not seeing the signal, but that seems
|
||||
* not to be the case, and so restoring a handler that will allow the task
|
||||
* to crash is not a good idea, and so the line below has been commented
|
||||
* out.
|
||||
*
|
||||
* sigaction (SIGUSR2,detailsPtr->sigHandlerAddr,NULL);
|
||||
*/
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/*
|
||||
* t i m e o u t T h r e a d M a i n
|
||||
*
|
||||
* This internal routine is the main code for the timeout thread.
|
||||
* The single argument is a pointer to a timeoutDetails structure that has
|
||||
* all the thread needs to know - thread to signal, delay time, and the
|
||||
* address of a flag to set if it triggers a timeout.
|
||||
*/
|
||||
|
||||
static void* timeoutThreadMain (void* passedPtr)
|
||||
{
|
||||
void* Return = (void*) 0;
|
||||
|
||||
/* We grab all the data held in the calling thread right now. In some
|
||||
* cases, we find that the calling thread has vanished and released
|
||||
* its memory, including the details structure, by the time the timeout
|
||||
* expires, and then we get an access violation when we try to set the
|
||||
* 'timed out' flag.
|
||||
*/
|
||||
|
||||
timeoutDetails details = *((timeoutDetails*) passedPtr);
|
||||
struct timespec requestedDelay = details.delay;
|
||||
|
||||
/* We do a nanosleep() for the specified delay, and then trigger a
|
||||
* timeout. Note that we allow for the case where the nanosleep() is
|
||||
* interrupted, and restart it for the remaining time. If the
|
||||
* thread that is doing the sem_wait() call gets the semaphore, it
|
||||
* will cancel this thread, which is fine as we aren't doing anything
|
||||
* other than a sleep and a signal.
|
||||
*/
|
||||
|
||||
for (;;) {
|
||||
struct timespec remainingDelay;
|
||||
if (nanosleep (&requestedDelay,&remainingDelay) == 0) {
|
||||
break;
|
||||
} else if (errno == EINTR) {
|
||||
requestedDelay = remainingDelay;
|
||||
} else {
|
||||
Return = (void*) errno;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* We've completed the delay without being cancelled, so we now trigger
|
||||
* the timeout by sending a signal to the calling thread. And that's it,
|
||||
* although we set the timeout flag first to indicate that it was us
|
||||
* that interrupted the sem_wait() call. One precaution: before we
|
||||
* try to set the timed-out flag, make sure the calling thread still
|
||||
* exists - this may not be the case if things are closing down a bit
|
||||
* messily. We check this quickly using a zero test signal.
|
||||
*/
|
||||
|
||||
if (pthread_kill(details.callingThread,0) == 0) {
|
||||
*(details.timedOutShort) = TRUE;
|
||||
if (triggerSignal (SIGUSR2,details.callingThread) < 0) {
|
||||
Return = (void*) errno;
|
||||
}
|
||||
}
|
||||
|
||||
return Return;
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/*
|
||||
* t r i g g e r S i g n a l
|
||||
*
|
||||
* This is a general purpose routine that sends a specified signal to
|
||||
* a specified thread, setting up a signal handler that does nothing,
|
||||
* and then giving the signal. The only effect will be to interrupt any
|
||||
* operation that is currently blocking - in this case, we expect this to
|
||||
* be a sem_wait() call.
|
||||
*/
|
||||
|
||||
static int triggerSignal (int Signal, pthread_t Thread)
|
||||
{
|
||||
int Result = 0;
|
||||
struct sigaction SignalDetails;
|
||||
SignalDetails.sa_handler = ignoreSignal;
|
||||
SignalDetails.sa_flags = 0;
|
||||
(void) sigemptyset(&SignalDetails.sa_mask);
|
||||
if ((Result = sigaction(Signal,&SignalDetails,NULL)) == 0) {
|
||||
Result = pthread_kill(Thread,Signal);
|
||||
}
|
||||
return Result;
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/*
|
||||
* i g n o r e S i g n a l
|
||||
*
|
||||
* And this is the signal handler that does nothing. (It clears its argument,
|
||||
* but this has no effect and prevents a compiler warning about an unused
|
||||
* argument.)
|
||||
*/
|
||||
|
||||
static void ignoreSignal (int Signal) {
|
||||
Signal = 0;
|
||||
}
|
||||
|
||||
#endif
|
8
src/frontend/qt_sdl/sem_timedwait.h
Normal file
8
src/frontend/qt_sdl/sem_timedwait.h
Normal file
@ -0,0 +1,8 @@
|
||||
#ifndef __SEM_TIMEDWAIT_H
|
||||
#define __SEM_TIMEDWAIT_H
|
||||
|
||||
#ifdef __APPLE__
|
||||
int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout);
|
||||
#endif
|
||||
|
||||
#endif
|
Reference in New Issue
Block a user