GB SIO: Game Boy Printer

This commit is contained in:
Vicki Pfau 2017-07-30 17:17:58 -07:00
parent 86901d93b6
commit dc976eaf51
12 changed files with 658 additions and 3 deletions

View File

@ -3,6 +3,7 @@ Features:
- ELF support
- Game Boy Camera support
- Qt: Set default Game Boy colors
- Game Boy Printer support
Bugfixes:
- GB Audio: Make audio unsigned with bias (fixes mgba.io/i/749)
- Python: Fix importing .gb or .gba before .core

View File

@ -57,7 +57,7 @@ file(GLOB GUI_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/util/gui/*.c ${CMAKE_CURRENT_S
file(GLOB GBA_RENDERER_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/gba/renderers/*.c)
file(GLOB GBA_SIO_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/gba/sio/lockstep.c)
file(GLOB GBA_EXTRA_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/gba/extra/*.c)
file(GLOB GB_SIO_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/gb/sio/lockstep.c)
file(GLOB GB_SIO_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/gb/sio/*.c)
file(GLOB GB_RENDERER_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/gb/renderers/*.c)
file(GLOB GB_EXTRA_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/gb/extra/*.c)
file(GLOB THIRD_PARTY_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/third-party/inih/*.c)

View File

@ -0,0 +1,74 @@
/* Copyright (c) 2013-2017 Jeffrey Pfau
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef GB_PRINTER_H
#define GB_PRINTER_H
#include <mgba-util/common.h>
CXX_GUARD_START
#include <mgba/gb/interface.h>
enum GBPrinterPacketByte {
GB_PRINTER_BYTE_MAGIC_0,
GB_PRINTER_BYTE_MAGIC_1,
GB_PRINTER_BYTE_COMMAND,
GB_PRINTER_BYTE_COMPRESSION,
GB_PRINTER_BYTE_LENGTH_0,
GB_PRINTER_BYTE_LENGTH_1,
GB_PRINTER_BYTE_DATA,
GB_PRINTER_BYTE_CHECKSUM_0,
GB_PRINTER_BYTE_CHECKSUM_1,
GB_PRINTER_BYTE_KEEPALIVE,
GB_PRINTER_BYTE_STATUS,
GB_PRINTER_BYTE_COMPRESSED_DATUM,
GB_PRINTER_BYTE_UNCOMPRESSED_DATA
};
enum GBPrinterStatus {
GB_PRINTER_STATUS_CHECKSUM_ERROR = 0x01,
GB_PRINTER_STATUS_PRINTING = 0x02,
GB_PRINTER_STATUS_PRINT_REQ = 0x04,
GB_PRINTER_STATUS_READY = 0x08,
GB_PRINTER_STATUS_LOW_BATTERY = 0x10,
GB_PRINTER_STATUS_TIMEOUT = 0x20,
GB_PRINTER_STATUS_PAPER_JAM = 0x40,
GB_PRINTER_STATUS_TEMPERATURE_ISSUE = 0x80
};
enum GBPrinterCommand {
GB_PRINTER_COMMAND_INIT = 0x1,
GB_PRINTER_COMMAND_PRINT = 0x2,
GB_PRINTER_COMMAND_DATA = 0x4,
GB_PRINTER_COMMAND_STATUS = 0xF,
};
struct GBPrinter {
struct GBSIODriver d;
void (*print)(struct GBPrinter*, int height, const uint8_t* data);
uint8_t* buffer;
uint16_t checksum;
enum GBPrinterCommand command;
uint16_t remainingBytes;
uint8_t remainingCmpBytes;
unsigned currentIndex;
bool compression;
uint8_t byte;
enum GBPrinterPacketByte next;
uint8_t status;
int printWait;
};
void GBPrinterCreate(struct GBPrinter* printer);
void GBPrinterDonePrinting(struct GBPrinter* printer);
CXX_GUARD_END
#endif

233
src/gb/sio/printer.c Normal file
View File

@ -0,0 +1,233 @@
/* Copyright (c) 2013-2017 Jeffrey Pfau
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include <mgba/internal/gb/sio/printer.h>
#include <mgba/internal/gb/gb.h>
#include <mgba/internal/gb/io.h>
static bool GBPrinterInit(struct GBSIODriver* driver);
static void GBPrinterDeinit(struct GBSIODriver* driver);
static void GBPrinterWriteSB(struct GBSIODriver* driver, uint8_t value);
static uint8_t GBPrinterWriteSC(struct GBSIODriver* driver, uint8_t value);
void GBPrinterCreate(struct GBPrinter* printer) {
printer->d.init = GBPrinterInit;
printer->d.deinit = GBPrinterDeinit;
printer->d.writeSB = GBPrinterWriteSB;
printer->d.writeSC = GBPrinterWriteSC;
printer->print = NULL;
}
bool GBPrinterInit(struct GBSIODriver* driver) {
struct GBPrinter* printer = (struct GBPrinter*) driver;
printer->checksum = 0;
printer->command = 0;
printer->remainingBytes = 0;
printer->currentIndex = 0;
printer->compression = false;
printer->byte = 0;
printer->next = GB_PRINTER_BYTE_MAGIC_0;
printer->status = 0;
printer->printWait = -1;
printer->buffer = malloc(GB_VIDEO_HORIZONTAL_PIXELS * GB_VIDEO_VERTICAL_PIXELS / 2);
return true;
}
void GBPrinterDeinit(struct GBSIODriver* driver) {
struct GBPrinter* printer = (struct GBPrinter*) driver;
free(printer->buffer);
}
static void GBPrinterWriteSB(struct GBSIODriver* driver, uint8_t value) {
struct GBPrinter* printer = (struct GBPrinter*) driver;
printer->byte = value;
}
static void _processByte(struct GBPrinter* printer) {
switch (printer->command) {
case GB_PRINTER_COMMAND_DATA:
if (printer->currentIndex < GB_VIDEO_VERTICAL_PIXELS * GB_VIDEO_HORIZONTAL_PIXELS / 2) {
printer->buffer[printer->currentIndex] = printer->byte;
++printer->currentIndex;
}
break;
case GB_PRINTER_COMMAND_PRINT:
// TODO
break;
default:
break;
}
}
static uint8_t GBPrinterWriteSC(struct GBSIODriver* driver, uint8_t value) {
struct GBPrinter* printer = (struct GBPrinter*) driver;
if ((value & 0x81) == 0x81) {
switch (printer->next) {
driver->p->pendingSB = 0;
case GB_PRINTER_BYTE_MAGIC_0:
if (printer->byte == 0x88) {
printer->next = GB_PRINTER_BYTE_MAGIC_1;
} else {
printer->next = GB_PRINTER_BYTE_MAGIC_0;
}
break;
case GB_PRINTER_BYTE_MAGIC_1:
if (printer->byte == 0x33) {
printer->next = GB_PRINTER_BYTE_COMMAND;
} else {
printer->next = GB_PRINTER_BYTE_MAGIC_0;
}
break;
case GB_PRINTER_BYTE_COMMAND:
printer->checksum = printer->byte;
printer->command = printer->byte;
printer->next = GB_PRINTER_BYTE_COMPRESSION;
break;
case GB_PRINTER_BYTE_COMPRESSION:
printer->checksum += printer->byte;
printer->compression = printer->byte;
printer->next = GB_PRINTER_BYTE_LENGTH_0;
break;
case GB_PRINTER_BYTE_LENGTH_0:
printer->checksum += printer->byte;
printer->remainingBytes = printer->byte;
printer->next = GB_PRINTER_BYTE_LENGTH_1;
break;
case GB_PRINTER_BYTE_LENGTH_1:
printer->checksum += printer->byte;
printer->remainingBytes |= printer->byte << 8;
if (printer->remainingBytes) {
printer->next = GB_PRINTER_BYTE_DATA;
} else {
printer->next = GB_PRINTER_BYTE_CHECKSUM_0;
}
switch (printer->command) {
case GB_PRINTER_COMMAND_INIT:
printer->currentIndex = 0;
printer->status &= ~(GB_PRINTER_STATUS_PRINT_REQ | GB_PRINTER_STATUS_READY);
break;
default:
break;
}
break;
case GB_PRINTER_BYTE_DATA:
printer->checksum += printer->byte;
if (!printer->compression) {
_processByte(printer);
} else {
printer->next = printer->byte & 0x80 ? GB_PRINTER_BYTE_COMPRESSED_DATUM : GB_PRINTER_BYTE_UNCOMPRESSED_DATA;
printer->remainingCmpBytes = (printer->byte & 0x7F) + 1;
if (printer->byte & 0x80) {
++printer->remainingCmpBytes;
}
}
--printer->remainingBytes;
if (!printer->remainingBytes) {
printer->next = GB_PRINTER_BYTE_CHECKSUM_0;
}
break;
case GB_PRINTER_BYTE_UNCOMPRESSED_DATA:
printer->checksum += printer->byte;
_processByte(printer);
--printer->remainingCmpBytes;
if (!printer->remainingCmpBytes) {
printer->next = GB_PRINTER_BYTE_DATA;
}
--printer->remainingBytes;
if (!printer->remainingBytes) {
printer->next = GB_PRINTER_BYTE_CHECKSUM_0;
}
break;
case GB_PRINTER_BYTE_COMPRESSED_DATUM:
printer->checksum += printer->byte;
while (printer->remainingCmpBytes) {
_processByte(printer);
--printer->remainingCmpBytes;
}
--printer->remainingBytes;
if (!printer->remainingBytes) {
printer->next = GB_PRINTER_BYTE_CHECKSUM_0;
} else {
printer->next = GB_PRINTER_BYTE_DATA;
}
break;
case GB_PRINTER_BYTE_CHECKSUM_0:
printer->checksum ^= printer->byte;
printer->next = GB_PRINTER_BYTE_CHECKSUM_1;
break;
case GB_PRINTER_BYTE_CHECKSUM_1:
printer->checksum ^= printer->byte << 8;
printer->next = GB_PRINTER_BYTE_KEEPALIVE;
break;
case GB_PRINTER_BYTE_KEEPALIVE:
driver->p->pendingSB = 0x81;
printer->next = GB_PRINTER_BYTE_STATUS;
break;
case GB_PRINTER_BYTE_STATUS:
switch (printer->command) {
case GB_PRINTER_COMMAND_DATA:
if (printer->currentIndex >= 0x280 && !(printer->status & GB_PRINTER_STATUS_CHECKSUM_ERROR)) {
printer->status |= GB_PRINTER_STATUS_READY;
}
break;
case GB_PRINTER_COMMAND_PRINT:
if (printer->currentIndex >= GB_VIDEO_HORIZONTAL_PIXELS * 2) {
printer->printWait = 0;
}
break;
case GB_PRINTER_COMMAND_STATUS:
if (!printer->printWait) {
printer->status &= ~GB_PRINTER_STATUS_READY;
printer->status |= GB_PRINTER_STATUS_PRINTING | GB_PRINTER_STATUS_PRINT_REQ;
if (printer->print) {
size_t y;
for (y = 0; y < printer->currentIndex / (2 * GB_VIDEO_HORIZONTAL_PIXELS); ++y) {
uint8_t lineBuffer[GB_VIDEO_HORIZONTAL_PIXELS * 2];
uint8_t* buffer = &printer->buffer[sizeof(lineBuffer) * y];
size_t i;
for (i = 0; i < sizeof(lineBuffer); i += 2) {
uint8_t ilo = buffer[i + 0x0];
uint8_t ihi = buffer[i + 0x1];
uint8_t olo = 0;
uint8_t ohi = 0;
olo |= ((ihi & 0x80) >> 0) | ((ilo & 0x80) >> 1);
olo |= ((ihi & 0x40) >> 1) | ((ilo & 0x40) >> 2);
olo |= ((ihi & 0x20) >> 2) | ((ilo & 0x20) >> 3);
olo |= ((ihi & 0x10) >> 3) | ((ilo & 0x10) >> 4);
ohi |= ((ihi & 0x08) << 4) | ((ilo & 0x08) << 3);
ohi |= ((ihi & 0x04) << 3) | ((ilo & 0x04) << 2);
ohi |= ((ihi & 0x02) << 2) | ((ilo & 0x02) << 1);
ohi |= ((ihi & 0x01) << 1) | ((ilo & 0x01) << 0);
lineBuffer[(((i >> 1) & 0x7) * GB_VIDEO_HORIZONTAL_PIXELS / 4) + ((i >> 3) & ~1)] = olo;
lineBuffer[(((i >> 1) & 0x7) * GB_VIDEO_HORIZONTAL_PIXELS / 4) + ((i >> 3) | 1)] = ohi;
}
memcpy(buffer, lineBuffer, sizeof(lineBuffer));
}
printer->print(printer, printer->currentIndex * 4 / GB_VIDEO_HORIZONTAL_PIXELS, printer->buffer);
}
}
if (printer->printWait >= 0) {
--printer->printWait;
}
default:
break;
}
driver->p->pendingSB = printer->status;
printer->next = GB_PRINTER_BYTE_MAGIC_0;
break;
}
printer->byte = 0;
}
return value;
}
void GBPrinterDonePrinting(struct GBPrinter* printer) {
printer->status &= ~GB_PRINTER_STATUS_PRINTING;
}

View File

@ -93,6 +93,7 @@ set(SOURCE_FILES
ObjView.cpp
OverrideView.cpp
PaletteView.cpp
PrinterView.cpp
ROMInfo.cpp
SavestateButton.cpp
SensorView.cpp
@ -123,6 +124,7 @@ set(UI_FILES
ObjView.ui
OverrideView.ui
PaletteView.ui
PrinterView.ui
ROMInfo.ui
SensorView.ui
SettingsView.ui

View File

@ -590,6 +590,59 @@ void CoreController::exportSharkport(const QString& path) {
#endif
}
void CoreController::attachPrinter() {
#ifdef M_CORE_GB
if (platform() != PLATFORM_GB) {
return;
}
GB* gb = static_cast<GB*>(m_threadContext.core->board);
clearMultiplayerController();
GBPrinterCreate(&m_printer.d);
m_printer.parent = this;
m_printer.d.print = [](GBPrinter* printer, int height, const uint8_t* data) {
QGBPrinter* qPrinter = reinterpret_cast<QGBPrinter*>(printer);
QImage image(GB_VIDEO_HORIZONTAL_PIXELS, height, QImage::Format_Indexed8);
QVector<QRgb> colors;
colors.append(qRgb(0xF8, 0xF8, 0xF8));
colors.append(qRgb(0xA8, 0xA8, 0xA8));
colors.append(qRgb(0x50, 0x50, 0x50));
colors.append(qRgb(0x00, 0x00, 0x00));
image.setColorTable(colors);
for (int y = 0; y < height; ++y) {
for (int x = 0; x < GB_VIDEO_HORIZONTAL_PIXELS; x += 4) {
uint8_t byte = data[(x + y * GB_VIDEO_HORIZONTAL_PIXELS) / 4];
image.setPixel(x + 0, y, (byte & 0xC0) >> 6);
image.setPixel(x + 1, y, (byte & 0x30) >> 4);
image.setPixel(x + 2, y, (byte & 0x0C) >> 2);
image.setPixel(x + 3, y, (byte & 0x03) >> 0);
}
}
QMetaObject::invokeMethod(qPrinter->parent, "imagePrinted", Q_ARG(const QImage&, image));
};
GBSIOSetDriver(&gb->sio, &m_printer.d.d);
#endif
}
void CoreController::detachPrinter() {
#ifdef M_CORE_GB
if (platform() != PLATFORM_GB) {
return;
}
GB* gb = static_cast<GB*>(m_threadContext.core->board);
GBPrinterDonePrinting(&m_printer.d);
GBSIOSetDriver(&gb->sio, nullptr);
#endif
}
void CoreController::endPrint() {
#ifdef M_CORE_GB
if (platform() != PLATFORM_GB) {
return;
}
GBPrinterDonePrinting(&m_printer.d);
#endif
}
void CoreController::setAVStream(mAVStream* stream) {
Interrupter interrupter(this);
m_threadContext.core->setAVStream(m_threadContext.core, stream);

View File

@ -22,6 +22,10 @@
#include <mgba/core/thread.h>
#include <mgba/core/tile-cache.h>
#ifdef M_CORE_GB
#include <mgba/internal/gb/sio/printer.h>
#endif
struct mCore;
namespace QGBA {
@ -123,6 +127,10 @@ public slots:
void importSharkport(const QString& path);
void exportSharkport(const QString& path);
void attachPrinter();
void detachPrinter();
void endPrint();
void setAVStream(mAVStream*);
void clearAVStream();
@ -149,6 +157,8 @@ signals:
void statusPosted(const QString& message);
void logPosted(int level, int category, const QString& log);
void imagePrinted(const QImage&);
private:
void updateKeys();
int updateAutofire();
@ -195,6 +205,13 @@ private:
mVideoLogContext* m_vl = nullptr;
VFile* m_vlVf = nullptr;
#ifdef M_CORE_GB
struct QGBPrinter {
GBPrinter d;
CoreController* parent;
} m_printer;
#endif
};
}

View File

@ -233,6 +233,9 @@ bool MultiplayerController::attachGame(CoreController* controller) {
}
void MultiplayerController::detachGame(CoreController* controller) {
if (m_players.empty()) {
return;
}
mCoreThread* thread = controller->thread();
if (!thread) {
return;

View File

@ -0,0 +1,73 @@
/* Copyright (c) 2013-2017 Jeffrey Pfau
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "PrinterView.h"
#include "CoreController.h"
#include "GBAApp.h"
#include <QPainter>
using namespace QGBA;
PrinterView::PrinterView(std::shared_ptr<CoreController> controller, QWidget* parent)
: QDialog(parent)
, m_controller(controller)
{
m_ui.setupUi(this);
connect(controller.get(), &CoreController::imagePrinted, this, &PrinterView::printImage);
connect(&m_timer, &QTimer::timeout, this, &PrinterView::printLine);
connect(m_ui.hurry, &QAbstractButton::clicked, this, &PrinterView::printAll);
connect(m_ui.tear, &QAbstractButton::clicked, this, &PrinterView::clear);
connect(m_ui.buttonBox, &QDialogButtonBox::accepted, this, &PrinterView::save);
m_timer.setInterval(80);
clear();
}
PrinterView::~PrinterView() {
m_controller->detachPrinter();
}
void PrinterView::save() {
QString filename = GBAApp::app()->getSaveFileName(this, tr("Save Printout"), tr("Portable Network Graphics (*.png)"));
if (filename.isNull()) {
return;
}
m_image.save(filename);
}
void PrinterView::clear() {
m_ui.image->setFixedHeight(0);
m_ui.image->clear();
m_ui.buttonBox->button(QDialogButtonBox::Save)->setEnabled(false);
}
void PrinterView::printImage(const QImage& image) {
QPixmap pixmap(image.width(), image.height() + m_image.height());
QPainter painter(&pixmap);
painter.drawPixmap(0, 0, m_image);
painter.drawImage(0, m_image.height(), image);
m_image = pixmap;
m_ui.image->setPixmap(m_image);
m_timer.start();
m_ui.hurry->setEnabled(true);
}
void PrinterView::printLine() {
m_ui.image->setFixedHeight(m_ui.image->height() + 1);
m_ui.scrollArea->ensureVisible(0, m_ui.image->height(), 0, 0);
if (m_ui.image->height() >= m_image.height()) {
printAll();
}
}
void PrinterView::printAll() {
m_timer.stop();
m_ui.image->setFixedHeight(m_image.height());
m_controller->endPrint();
m_ui.buttonBox->button(QDialogButtonBox::Save)->setEnabled(true);
m_ui.hurry->setEnabled(false);
}

View File

@ -0,0 +1,50 @@
/* Copyright (c) 2013-2017 Jeffrey Pfau
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef QGBA_PRINTER_VIEW
#define QGBA_PRINTER_VIEW
#include <QDialog>
#include <QPixmap>
#include <QTimer>
#include <memory>
#include "ui_PrinterView.h"
namespace QGBA {
class CoreController;
class PrinterView : public QDialog {
Q_OBJECT
public:
PrinterView(std::shared_ptr<CoreController> controller, QWidget* parent = nullptr);
~PrinterView();
signals:
void donePrinting();
public slots:
void save();
void clear();
private slots:
void printImage(const QImage&);
void printLine();
void printAll();
private:
Ui::PrinterView m_ui;
QPixmap m_image;
QTimer m_timer;
std::shared_ptr<CoreController> m_controller;
};
}
#endif

View File

@ -0,0 +1,137 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>PrinterView</class>
<widget class="QWidget" name="PrinterView">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>241</width>
<height>311</height>
</rect>
</property>
<property name="windowTitle">
<string>Game Boy Printer</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item alignment="Qt::AlignHCenter">
<widget class="QScrollArea" name="scrollArea">
<property name="frameShape">
<enum>QFrame::NoFrame</enum>
</property>
<property name="widgetResizable">
<bool>true</bool>
</property>
<widget class="QWidget" name="scrollAreaWidgetContents">
<layout class="QVBoxLayout" name="verticalLayout_2">
<property name="spacing">
<number>0</number>
</property>
<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>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
</spacer>
</item>
<item>
<widget class="QLabel" name="image">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>160</width>
<height>1</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>160</width>
<height>16777215</height>
</size>
</property>
<property name="alignment">
<set>Qt::AlignHCenter|Qt::AlignTop</set>
</property>
</widget>
</item>
<item>
<widget class="Line" name="line">
<property name="frameShadow">
<enum>QFrame::Plain</enum>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
</layout>
</widget>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QPushButton" name="hurry">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Hurry up!</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="tear">
<property name="text">
<string>Tear off</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="standardButtons">
<set>QDialogButtonBox::Close|QDialogButtonBox::Save</set>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>PrinterView</receiver>
<slot>close()</slot>
<hints>
<hint type="sourcelabel">
<x>112</x>
<y>226</y>
</hint>
<hint type="destinationlabel">
<x>112</x>
<y>123</y>
</hint>
</hints>
</connection>
</connections>
</ui>

View File

@ -41,6 +41,7 @@
#include "OverrideView.h"
#include "ObjView.h"
#include "PaletteView.h"
#include "PrinterView.h"
#include "ROMInfo.h"
#include "SensorView.h"
#include "SettingsView.h"
@ -1358,9 +1359,7 @@ void Window::setupMenu(QMenuBar* menubar) {
fpsTargetOption->addValue(tr("240"), 240, target);
m_config->updateOption("fpsTarget");
#if defined(USE_PNG) || defined(USE_FFMPEG) || defined(USE_MAGICK)
avMenu->addSeparator();
#endif
#ifdef USE_PNG
QAction* screenshot = new QAction(tr("Take &screenshot"), avMenu);
@ -1397,6 +1396,18 @@ void Window::setupMenu(QMenuBar* menubar) {
addControlledAction(avMenu, stopVL, "stopVL");
m_gameActions.append(stopVL);
#ifdef M_CORE_GB
QAction* gbPrint = new QAction(tr("Game Boy Printer..."), avMenu);
connect(gbPrint, &QAction::triggered, [this]() {
PrinterView* view = new PrinterView(m_controller);
openView(view);
m_controller->attachPrinter();
});
addControlledAction(avMenu, gbPrint, "gbPrint");
m_gameActions.append(gbPrint);
#endif
avMenu->addSeparator();
m_videoLayers = avMenu->addMenu(tr("Video layers"));
m_shortcutController->addMenu(m_videoLayers, avMenu);
@ -1807,6 +1818,7 @@ void Window::setController(CoreController* controller, const QString& fname) {
}
m_controller->start();
m_controller->loadConfig(m_config);
}
WindowBackground::WindowBackground(QWidget* parent)