mirror of https://github.com/mgba-emu/mgba.git
Core: Video log recording
This commit is contained in:
parent
bed6ba1fc4
commit
73947766de
|
@ -26,10 +26,10 @@ CXX_GUARD_START
|
|||
enum mPlatform {
|
||||
PLATFORM_NONE = -1,
|
||||
#ifdef M_CORE_GBA
|
||||
PLATFORM_GBA,
|
||||
PLATFORM_GBA = 0,
|
||||
#endif
|
||||
#ifdef M_CORE_GB
|
||||
PLATFORM_GB,
|
||||
PLATFORM_GB = 1,
|
||||
#endif
|
||||
};
|
||||
|
||||
|
@ -40,6 +40,7 @@ enum mCoreChecksumType {
|
|||
struct mCoreConfig;
|
||||
struct mCoreSync;
|
||||
struct mStateExtdata;
|
||||
struct mVideoLogContext;
|
||||
struct mCore {
|
||||
void* cpu;
|
||||
void* board;
|
||||
|
@ -145,6 +146,9 @@ struct mCore {
|
|||
size_t (*listAudioChannels)(const struct mCore*, const struct mCoreChannelInfo**);
|
||||
void (*enableVideoLayer)(struct mCore*, size_t id, bool enable);
|
||||
void (*enableAudioChannel)(struct mCore*, size_t id, bool enable);
|
||||
|
||||
void (*startVideoLog)(struct mCore*, struct mVideoLogContext*);
|
||||
void (*endVideoLog)(struct mCore*);
|
||||
};
|
||||
|
||||
#if !defined(MINIMAL_CORE) || MINIMAL_CORE < 2
|
||||
|
|
|
@ -27,6 +27,7 @@ struct mVideoLoggerDirtyInfo {
|
|||
uint32_t padding;
|
||||
};
|
||||
|
||||
struct VFile;
|
||||
struct mVideoLogger {
|
||||
bool (*writeData)(struct mVideoLogger* logger, const void* data, size_t length);
|
||||
bool (*readData)(struct mVideoLogger* logger, void* data, size_t length, bool block);
|
||||
|
@ -45,8 +46,40 @@ struct mVideoLogger {
|
|||
uint16_t* vram;
|
||||
uint16_t* oam;
|
||||
uint16_t* palette;
|
||||
|
||||
struct VFile* vf;
|
||||
};
|
||||
|
||||
struct mVideoLogChannel {
|
||||
uint32_t type;
|
||||
void* initialState;
|
||||
size_t initialStateSize;
|
||||
struct VFile* channelData;
|
||||
};
|
||||
|
||||
struct mVideoLogContext {
|
||||
void* initialState;
|
||||
size_t initialStateSize;
|
||||
uint32_t nChannels;
|
||||
struct mVideoLogChannel channels[32];
|
||||
};
|
||||
|
||||
struct mVideoLogHeader {
|
||||
char magic[4];
|
||||
uint32_t platform;
|
||||
uint32_t nChannels;
|
||||
uint32_t initialStatePointer;
|
||||
uint32_t channelPointers[32];
|
||||
};
|
||||
|
||||
struct mVideoLogChannelHeader {
|
||||
uint32_t type;
|
||||
uint32_t channelInitialStatePointer;
|
||||
uint32_t channelSize;
|
||||
uint32_t reserved;
|
||||
};
|
||||
|
||||
void mVideoLoggerRendererCreate(struct mVideoLogger* logger);
|
||||
void mVideoLoggerRendererInit(struct mVideoLogger* logger);
|
||||
void mVideoLoggerRendererDeinit(struct mVideoLogger* logger);
|
||||
void mVideoLoggerRendererReset(struct mVideoLogger* logger);
|
||||
|
@ -61,6 +94,11 @@ void mVideoLoggerRendererFlush(struct mVideoLogger* logger);
|
|||
|
||||
bool mVideoLoggerRendererRun(struct mVideoLogger* logger);
|
||||
|
||||
struct mCore;
|
||||
struct mVideoLogContext* mVideoLoggerCreate(struct mCore* core);
|
||||
void mVideoLoggerDestroy(struct mCore* core, struct mVideoLogContext*);
|
||||
void mVideoLoggerWrite(struct mCore* core, struct mVideoLogContext*, struct VFile*);
|
||||
|
||||
CXX_GUARD_END
|
||||
|
||||
#endif
|
||||
|
|
|
@ -31,6 +31,8 @@ struct GBAVideoProxyRenderer {
|
|||
};
|
||||
|
||||
void GBAVideoProxyRendererCreate(struct GBAVideoProxyRenderer* renderer, struct GBAVideoRenderer* backend);
|
||||
void GBAVideoProxyRendererShim(struct GBAVideo* video, struct GBAVideoProxyRenderer* renderer);
|
||||
void GBAVideoProxyRendererUnshim(struct GBAVideo* video, struct GBAVideoProxyRenderer* renderer);
|
||||
|
||||
CXX_GUARD_END
|
||||
|
||||
|
|
|
@ -5,13 +5,26 @@
|
|||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#include <mgba/core/video-logger.h>
|
||||
|
||||
#include <mgba/core/core.h>
|
||||
#include <mgba-util/memory.h>
|
||||
#include <mgba-util/vfs.h>
|
||||
|
||||
const char mVL_MAGIC[] = "mVL\0";
|
||||
|
||||
static bool _writeData(struct mVideoLogger* logger, const void* data, size_t length);
|
||||
static bool _readData(struct mVideoLogger* logger, void* data, size_t length, bool block);
|
||||
|
||||
static inline size_t _roundUp(size_t value, int shift) {
|
||||
value += (1 << shift) - 1;
|
||||
return value >> shift;
|
||||
}
|
||||
|
||||
void mVideoLoggerRendererCreate(struct mVideoLogger* logger) {
|
||||
logger->writeData = _writeData;
|
||||
logger->readData = _readData;
|
||||
logger->vf = NULL;
|
||||
}
|
||||
|
||||
void mVideoLoggerRendererInit(struct mVideoLogger* logger) {
|
||||
logger->palette = anonymousMemoryMap(logger->paletteSize);
|
||||
logger->vram = anonymousMemoryMap(logger->vramSize);
|
||||
|
@ -134,3 +147,79 @@ bool mVideoLoggerRendererRun(struct mVideoLogger* logger) {
|
|||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool _writeData(struct mVideoLogger* logger, const void* data, size_t length) {
|
||||
return logger->vf->write(logger->vf, data, length) == (ssize_t) length;
|
||||
}
|
||||
|
||||
static bool _readData(struct mVideoLogger* logger, void* data, size_t length, bool block) {
|
||||
return logger->vf->read(logger->vf, data, length) == (ssize_t) length || !block;
|
||||
}
|
||||
|
||||
struct mVideoLogContext* mVideoLoggerCreate(struct mCore* core) {
|
||||
struct mVideoLogContext* context = malloc(sizeof(*context));
|
||||
core->startVideoLog(core, context);
|
||||
return context;
|
||||
}
|
||||
|
||||
void mVideoLoggerDestroy(struct mCore* core, struct mVideoLogContext* context) {
|
||||
if (core) {
|
||||
core->endVideoLog(core);
|
||||
}
|
||||
free(context);
|
||||
}
|
||||
|
||||
void mVideoLoggerWrite(struct mCore* core, struct mVideoLogContext* context, struct VFile* vf) {
|
||||
struct mVideoLogHeader header = {{0}};
|
||||
memcpy(header.magic, mVL_MAGIC, sizeof(mVL_MAGIC));
|
||||
|
||||
enum mPlatform platform = core->platform(core);
|
||||
STORE_32LE(platform, 0, &header.platform);
|
||||
STORE_32LE(context->nChannels, 0, &header.nChannels);
|
||||
|
||||
ssize_t pointer = vf->seek(vf, sizeof(header), SEEK_SET);
|
||||
if (context->initialStateSize) {
|
||||
ssize_t written = vf->write(vf, context->initialState, context->initialStateSize);
|
||||
if (written > 0) {
|
||||
STORE_32LE(pointer, 0, &header.initialStatePointer);
|
||||
pointer += written;
|
||||
} else {
|
||||
header.initialStatePointer = 0;
|
||||
}
|
||||
} else {
|
||||
header.initialStatePointer = 0;
|
||||
}
|
||||
|
||||
size_t i;
|
||||
for (i = 0; i < context->nChannels && i < 32; ++i) {
|
||||
struct VFile* channel = context->channels[i].channelData;
|
||||
void* block = channel->map(channel, channel->size(channel), MAP_READ);
|
||||
|
||||
struct mVideoLogChannelHeader chHeader = {0};
|
||||
STORE_32LE(context->channels[i].type, 0, &chHeader.type);
|
||||
STORE_32LE(channel->size(channel), 0, &chHeader.channelSize);
|
||||
|
||||
if (context->channels[i].initialStateSize) {
|
||||
ssize_t written = vf->write(vf, context->channels[i].initialState, context->channels[i].initialStateSize);
|
||||
if (written > 0) {
|
||||
STORE_32LE(pointer, 0, &chHeader.channelInitialStatePointer);
|
||||
pointer += written;
|
||||
} else {
|
||||
chHeader.channelInitialStatePointer = 0;
|
||||
}
|
||||
}
|
||||
STORE_32LE(pointer, 0, &header.channelPointers[i]);
|
||||
ssize_t written = vf->write(vf, &chHeader, sizeof(chHeader));
|
||||
if (written != sizeof(chHeader)) {
|
||||
continue;
|
||||
}
|
||||
pointer += written;
|
||||
written = vf->write(vf, block, channel->size(channel));
|
||||
if (written != channel->size(channel)) {
|
||||
break;
|
||||
}
|
||||
pointer += written;
|
||||
}
|
||||
vf->seek(vf, 0, SEEK_SET);
|
||||
vf->write(vf, &header, sizeof(header));
|
||||
}
|
||||
|
|
|
@ -42,6 +42,8 @@ const static struct mCoreChannelInfo _GBAAudioChannels[] = {
|
|||
struct GBACore {
|
||||
struct mCore d;
|
||||
struct GBAVideoSoftwareRenderer renderer;
|
||||
struct GBAVideoProxyRenderer logProxy;
|
||||
struct mVideoLogContext* logContext;
|
||||
#ifndef DISABLE_THREADING
|
||||
struct GBAVideoThreadProxyRenderer threadProxy;
|
||||
int threadedVideo;
|
||||
|
@ -69,6 +71,7 @@ static bool _GBACoreInit(struct mCore* core) {
|
|||
gbacore->overrides = NULL;
|
||||
gbacore->debuggerPlatform = NULL;
|
||||
gbacore->cheatDevice = NULL;
|
||||
gbacore->logContext = NULL;
|
||||
|
||||
GBACreate(gba);
|
||||
// TODO: Restore cheats
|
||||
|
@ -646,6 +649,37 @@ static void _GBACoreEnableAudioChannel(struct mCore* core, size_t id, bool enabl
|
|||
}
|
||||
}
|
||||
|
||||
static void _GBACoreStartVideoLog(struct mCore* core, struct mVideoLogContext* context) {
|
||||
struct GBACore* gbacore = (struct GBACore*) core;
|
||||
struct GBA* gba = core->board;
|
||||
gbacore->logContext = context;
|
||||
|
||||
GBAVideoProxyRendererCreate(&gbacore->logProxy, gba->video.renderer);
|
||||
|
||||
context->initialStateSize = core->stateSize(core);
|
||||
context->initialState = anonymousMemoryMap(context->initialStateSize);
|
||||
core->saveState(core, context->initialState);
|
||||
|
||||
struct VFile* vf = VFileMemChunk(NULL, 0);
|
||||
context->nChannels = 1;
|
||||
context->channels[0].initialState = NULL;
|
||||
context->channels[0].initialStateSize = 0;
|
||||
context->channels[0].channelData = vf;
|
||||
gbacore->logProxy.logger.vf = vf;
|
||||
gbacore->logProxy.block = false;
|
||||
|
||||
GBAVideoProxyRendererShim(&gba->video, &gbacore->logProxy);
|
||||
}
|
||||
|
||||
static void _GBACoreEndVideoLog(struct mCore* core) {
|
||||
struct GBACore* gbacore = (struct GBACore*) core;
|
||||
struct GBA* gba = core->board;
|
||||
GBAVideoProxyRendererUnshim(&gba->video, &gbacore->logProxy);
|
||||
|
||||
mappedMemoryFree(gbacore->logContext->initialState, gbacore->logContext->initialStateSize);
|
||||
gbacore->logContext->channels[0].channelData->close(gbacore->logContext->channels[0].channelData);
|
||||
}
|
||||
|
||||
struct mCore* GBACoreCreate(void) {
|
||||
struct GBACore* gbacore = malloc(sizeof(*gbacore));
|
||||
struct mCore* core = &gbacore->d;
|
||||
|
@ -718,5 +752,7 @@ struct mCore* GBACoreCreate(void) {
|
|||
core->listAudioChannels = _GBACoreListAudioChannels;
|
||||
core->enableVideoLayer = _GBACoreEnableVideoLayer;
|
||||
core->enableAudioChannel = _GBACoreEnableAudioChannel;
|
||||
core->startVideoLog = _GBACoreStartVideoLog;
|
||||
core->endVideoLog = _GBACoreEndVideoLog;
|
||||
return core;
|
||||
}
|
||||
|
|
|
@ -25,6 +25,8 @@ static bool _parsePacket(struct mVideoLogger* logger, const struct mVideoLoggerD
|
|||
static uint16_t* _vramBlock(struct mVideoLogger* logger, uint32_t address);
|
||||
|
||||
void GBAVideoProxyRendererCreate(struct GBAVideoProxyRenderer* renderer, struct GBAVideoRenderer* backend) {
|
||||
mVideoLoggerRendererCreate(&renderer->logger);
|
||||
|
||||
renderer->d.init = GBAVideoProxyRendererInit;
|
||||
renderer->d.reset = GBAVideoProxyRendererReset;
|
||||
renderer->d.deinit = GBAVideoProxyRendererDeinit;
|
||||
|
@ -43,9 +45,11 @@ void GBAVideoProxyRendererCreate(struct GBAVideoProxyRenderer* renderer, struct
|
|||
renderer->d.disableBG[3] = false;
|
||||
renderer->d.disableOBJ = false;
|
||||
|
||||
renderer->init = NULL;
|
||||
renderer->deinit = NULL;
|
||||
renderer->reset = NULL;
|
||||
|
||||
renderer->logger.context = renderer;
|
||||
renderer->logger.writeData = NULL;
|
||||
renderer->logger.readData = NULL;
|
||||
renderer->logger.parsePacket = _parsePacket;
|
||||
renderer->logger.vramBlock = _vramBlock;
|
||||
renderer->logger.paletteSize = SIZE_PALETTE_RAM;
|
||||
|
@ -55,9 +59,7 @@ void GBAVideoProxyRendererCreate(struct GBAVideoProxyRenderer* renderer, struct
|
|||
renderer->backend = backend;
|
||||
}
|
||||
|
||||
void GBAVideoProxyRendererInit(struct GBAVideoRenderer* renderer) {
|
||||
struct GBAVideoProxyRenderer* proxyRenderer = (struct GBAVideoProxyRenderer*) renderer;
|
||||
|
||||
static void _init(struct GBAVideoProxyRenderer* proxyRenderer) {
|
||||
mVideoLoggerRendererInit(&proxyRenderer->logger);
|
||||
|
||||
if (proxyRenderer->block) {
|
||||
|
@ -67,20 +69,65 @@ void GBAVideoProxyRendererInit(struct GBAVideoRenderer* renderer) {
|
|||
proxyRenderer->backend->cache = NULL;
|
||||
}
|
||||
|
||||
proxyRenderer->init(proxyRenderer);
|
||||
if (proxyRenderer->init) {
|
||||
proxyRenderer->init(proxyRenderer);
|
||||
}
|
||||
}
|
||||
|
||||
static void _reset(struct GBAVideoProxyRenderer* proxyRenderer) {
|
||||
memcpy(proxyRenderer->logger.oam, &proxyRenderer->d.oam->raw, SIZE_OAM);
|
||||
memcpy(proxyRenderer->logger.palette, &proxyRenderer->d.palette, SIZE_PALETTE_RAM);
|
||||
memcpy(proxyRenderer->logger.vram, &proxyRenderer->d.vram, SIZE_VRAM);
|
||||
|
||||
mVideoLoggerRendererReset(&proxyRenderer->logger);
|
||||
|
||||
if (proxyRenderer->reset) {
|
||||
proxyRenderer->reset(proxyRenderer);
|
||||
}
|
||||
}
|
||||
|
||||
void GBAVideoProxyRendererShim(struct GBAVideo* video, struct GBAVideoProxyRenderer* renderer) {
|
||||
if (video->renderer != renderer->backend) {
|
||||
return;
|
||||
}
|
||||
renderer->d.cache = video->renderer->cache;
|
||||
video->renderer = &renderer->d;
|
||||
renderer->d.palette = video->palette;
|
||||
renderer->d.vram = video->vram;
|
||||
renderer->d.oam = &video->oam;
|
||||
_init(renderer);
|
||||
_reset(renderer);
|
||||
}
|
||||
|
||||
void GBAVideoProxyRendererUnshim(struct GBAVideo* video, struct GBAVideoProxyRenderer* renderer) {
|
||||
if (video->renderer != &renderer->d) {
|
||||
return;
|
||||
}
|
||||
renderer->backend->cache = video->renderer->cache;
|
||||
video->renderer = renderer->backend;
|
||||
renderer->backend->palette = video->palette;
|
||||
renderer->backend->vram = video->vram;
|
||||
renderer->backend->oam = &video->oam;
|
||||
|
||||
if (renderer->deinit) {
|
||||
renderer->deinit(renderer);
|
||||
}
|
||||
|
||||
mVideoLoggerRendererDeinit(&renderer->logger);
|
||||
}
|
||||
|
||||
void GBAVideoProxyRendererInit(struct GBAVideoRenderer* renderer) {
|
||||
struct GBAVideoProxyRenderer* proxyRenderer = (struct GBAVideoProxyRenderer*) renderer;
|
||||
|
||||
_init(proxyRenderer);
|
||||
|
||||
proxyRenderer->backend->init(proxyRenderer->backend);
|
||||
}
|
||||
|
||||
void GBAVideoProxyRendererReset(struct GBAVideoRenderer* renderer) {
|
||||
struct GBAVideoProxyRenderer* proxyRenderer = (struct GBAVideoProxyRenderer*) renderer;
|
||||
memcpy(proxyRenderer->logger.oam, &renderer->oam->raw, SIZE_OAM);
|
||||
memcpy(proxyRenderer->logger.palette, renderer->palette, SIZE_PALETTE_RAM);
|
||||
memcpy(proxyRenderer->logger.vram, renderer->vram, SIZE_VRAM);
|
||||
|
||||
mVideoLoggerRendererReset(&proxyRenderer->logger);
|
||||
|
||||
proxyRenderer->reset(proxyRenderer);
|
||||
_reset(proxyRenderer);
|
||||
|
||||
proxyRenderer->backend->reset(proxyRenderer->backend);
|
||||
}
|
||||
|
@ -88,7 +135,9 @@ void GBAVideoProxyRendererReset(struct GBAVideoRenderer* renderer) {
|
|||
void GBAVideoProxyRendererDeinit(struct GBAVideoRenderer* renderer) {
|
||||
struct GBAVideoProxyRenderer* proxyRenderer = (struct GBAVideoProxyRenderer*) renderer;
|
||||
|
||||
proxyRenderer->deinit(proxyRenderer);
|
||||
if (proxyRenderer->deinit) {
|
||||
proxyRenderer->deinit(proxyRenderer);
|
||||
}
|
||||
|
||||
proxyRenderer->backend->deinit(proxyRenderer->backend);
|
||||
|
||||
|
|
|
@ -39,6 +39,7 @@ void GBAVideoThreadProxyRendererCreate(struct GBAVideoThreadProxyRenderer* rende
|
|||
|
||||
renderer->d.logger.writeData = _writeData;
|
||||
renderer->d.logger.readData = _readData;
|
||||
renderer->d.logger.vf = NULL;
|
||||
}
|
||||
|
||||
void GBAVideoThreadProxyRendererInit(struct GBAVideoProxyRenderer* renderer) {
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
#include <mgba/core/directories.h>
|
||||
#include <mgba/core/serialize.h>
|
||||
#include <mgba/core/tile-cache.h>
|
||||
#include <mgba/core/video-logger.h>
|
||||
#ifdef M_CORE_GBA
|
||||
#include <mgba/gba/interface.h>
|
||||
#include <mgba/internal/gba/gba.h>
|
||||
|
@ -71,6 +72,7 @@ GameController::GameController(QObject* parent)
|
|||
, m_loadStateFlags(SAVESTATE_SCREENSHOT | SAVESTATE_RTC)
|
||||
, m_preload(false)
|
||||
, m_override(nullptr)
|
||||
, m_vl(nullptr)
|
||||
{
|
||||
#ifdef M_CORE_GBA
|
||||
m_lux.p = this;
|
||||
|
@ -1199,6 +1201,27 @@ void GameController::disableLogLevel(int levels) {
|
|||
m_logLevels &= ~levels;
|
||||
}
|
||||
|
||||
void GameController::startVideoLog(const QString& path) {
|
||||
if (!isLoaded() || m_vl) {
|
||||
return;
|
||||
}
|
||||
m_vlPath = path;
|
||||
m_vl = mVideoLoggerCreate(m_threadContext.core);
|
||||
}
|
||||
|
||||
void GameController::endVideoLog() {
|
||||
if (!m_vl) {
|
||||
return;
|
||||
}
|
||||
if (isLoaded()) {
|
||||
VFile* vf = VFileDevice::open(m_vlPath, O_WRONLY | O_CREAT | O_TRUNC);
|
||||
mVideoLoggerWrite(m_threadContext.core, m_vl, vf);
|
||||
vf->close(vf);
|
||||
}
|
||||
mVideoLoggerDestroy(m_threadContext.core, m_vl);
|
||||
m_vf = nullptr;
|
||||
}
|
||||
|
||||
void GameController::pollEvents() {
|
||||
if (!m_inputController) {
|
||||
return;
|
||||
|
|
|
@ -29,6 +29,7 @@ struct GBAAudio;
|
|||
struct mCoreConfig;
|
||||
struct mDebugger;
|
||||
struct mTileCache;
|
||||
struct mVideoLogContext;
|
||||
|
||||
namespace QGBA {
|
||||
|
||||
|
@ -174,6 +175,9 @@ public slots:
|
|||
void enableLogLevel(int);
|
||||
void disableLogLevel(int);
|
||||
|
||||
void startVideoLog(const QString& path);
|
||||
void endVideoLog();
|
||||
|
||||
private slots:
|
||||
void openGame(bool bios = false);
|
||||
void crashGame(const QString& crashMessage);
|
||||
|
@ -242,6 +246,9 @@ private:
|
|||
|
||||
mAVStream* m_stream;
|
||||
|
||||
mVideoLogContext* m_vl;
|
||||
QString m_vlPath;
|
||||
|
||||
struct GameControllerLux : GBALuminanceSource {
|
||||
GameController* p;
|
||||
uint8_t value;
|
||||
|
|
|
@ -478,6 +478,13 @@ void Window::openAboutScreen() {
|
|||
openView(about);
|
||||
}
|
||||
|
||||
void Window::startVideoLog() {
|
||||
QString filename = GBAApp::app()->getSaveFileName(this, tr("Select video log"), tr("Video logs (*.mvl)"));
|
||||
if (!filename.isEmpty()) {
|
||||
m_controller->startVideoLog(filename);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T, typename A>
|
||||
std::function<void()> Window::openTView(A arg) {
|
||||
return [=]() {
|
||||
|
@ -1350,6 +1357,16 @@ void Window::setupMenu(QMenuBar* menubar) {
|
|||
addControlledAction(avMenu, recordGIF, "recordGIF");
|
||||
#endif
|
||||
|
||||
QAction* recordVL = new QAction(tr("Record video log..."), avMenu);
|
||||
connect(recordVL, SIGNAL(triggered()), this, SLOT(startVideoLog()));
|
||||
addControlledAction(avMenu, recordVL, "recordVL");
|
||||
m_gameActions.append(recordVL);
|
||||
|
||||
QAction* stopVL = new QAction(tr("Stop video log"), avMenu);
|
||||
connect(stopVL, SIGNAL(triggered()), m_controller, SLOT(endVideoLog()));
|
||||
addControlledAction(avMenu, stopVL, "stopVL");
|
||||
m_gameActions.append(stopVL);
|
||||
|
||||
avMenu->addSeparator();
|
||||
m_videoLayers = avMenu->addMenu(tr("Video layers"));
|
||||
m_shortcutController->addMenu(m_videoLayers, avMenu);
|
||||
|
|
|
@ -81,6 +81,8 @@ public slots:
|
|||
void openSettingsWindow();
|
||||
void openAboutScreen();
|
||||
|
||||
void startVideoLog();
|
||||
|
||||
#ifdef USE_DEBUGGERS
|
||||
void consoleOpen();
|
||||
#endif
|
||||
|
|
Loading…
Reference in New Issue