mirror of https://github.com/mgba-emu/mgba.git
GB SIO: Game Boy Printer
This commit is contained in:
parent
86901d93b6
commit
dc976eaf51
1
CHANGES
1
CHANGES
|
@ -3,6 +3,7 @@ Features:
|
||||||
- ELF support
|
- ELF support
|
||||||
- Game Boy Camera support
|
- Game Boy Camera support
|
||||||
- Qt: Set default Game Boy colors
|
- Qt: Set default Game Boy colors
|
||||||
|
- Game Boy Printer support
|
||||||
Bugfixes:
|
Bugfixes:
|
||||||
- GB Audio: Make audio unsigned with bias (fixes mgba.io/i/749)
|
- GB Audio: Make audio unsigned with bias (fixes mgba.io/i/749)
|
||||||
- Python: Fix importing .gb or .gba before .core
|
- Python: Fix importing .gb or .gba before .core
|
||||||
|
|
|
@ -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_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_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 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_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 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)
|
file(GLOB THIRD_PARTY_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/third-party/inih/*.c)
|
||||||
|
|
|
@ -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
|
|
@ -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;
|
||||||
|
}
|
|
@ -93,6 +93,7 @@ set(SOURCE_FILES
|
||||||
ObjView.cpp
|
ObjView.cpp
|
||||||
OverrideView.cpp
|
OverrideView.cpp
|
||||||
PaletteView.cpp
|
PaletteView.cpp
|
||||||
|
PrinterView.cpp
|
||||||
ROMInfo.cpp
|
ROMInfo.cpp
|
||||||
SavestateButton.cpp
|
SavestateButton.cpp
|
||||||
SensorView.cpp
|
SensorView.cpp
|
||||||
|
@ -123,6 +124,7 @@ set(UI_FILES
|
||||||
ObjView.ui
|
ObjView.ui
|
||||||
OverrideView.ui
|
OverrideView.ui
|
||||||
PaletteView.ui
|
PaletteView.ui
|
||||||
|
PrinterView.ui
|
||||||
ROMInfo.ui
|
ROMInfo.ui
|
||||||
SensorView.ui
|
SensorView.ui
|
||||||
SettingsView.ui
|
SettingsView.ui
|
||||||
|
|
|
@ -590,6 +590,59 @@ void CoreController::exportSharkport(const QString& path) {
|
||||||
#endif
|
#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) {
|
void CoreController::setAVStream(mAVStream* stream) {
|
||||||
Interrupter interrupter(this);
|
Interrupter interrupter(this);
|
||||||
m_threadContext.core->setAVStream(m_threadContext.core, stream);
|
m_threadContext.core->setAVStream(m_threadContext.core, stream);
|
||||||
|
|
|
@ -22,6 +22,10 @@
|
||||||
#include <mgba/core/thread.h>
|
#include <mgba/core/thread.h>
|
||||||
#include <mgba/core/tile-cache.h>
|
#include <mgba/core/tile-cache.h>
|
||||||
|
|
||||||
|
#ifdef M_CORE_GB
|
||||||
|
#include <mgba/internal/gb/sio/printer.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
struct mCore;
|
struct mCore;
|
||||||
|
|
||||||
namespace QGBA {
|
namespace QGBA {
|
||||||
|
@ -123,6 +127,10 @@ public slots:
|
||||||
void importSharkport(const QString& path);
|
void importSharkport(const QString& path);
|
||||||
void exportSharkport(const QString& path);
|
void exportSharkport(const QString& path);
|
||||||
|
|
||||||
|
void attachPrinter();
|
||||||
|
void detachPrinter();
|
||||||
|
void endPrint();
|
||||||
|
|
||||||
void setAVStream(mAVStream*);
|
void setAVStream(mAVStream*);
|
||||||
void clearAVStream();
|
void clearAVStream();
|
||||||
|
|
||||||
|
@ -149,6 +157,8 @@ signals:
|
||||||
void statusPosted(const QString& message);
|
void statusPosted(const QString& message);
|
||||||
void logPosted(int level, int category, const QString& log);
|
void logPosted(int level, int category, const QString& log);
|
||||||
|
|
||||||
|
void imagePrinted(const QImage&);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void updateKeys();
|
void updateKeys();
|
||||||
int updateAutofire();
|
int updateAutofire();
|
||||||
|
@ -195,6 +205,13 @@ private:
|
||||||
|
|
||||||
mVideoLogContext* m_vl = nullptr;
|
mVideoLogContext* m_vl = nullptr;
|
||||||
VFile* m_vlVf = nullptr;
|
VFile* m_vlVf = nullptr;
|
||||||
|
|
||||||
|
#ifdef M_CORE_GB
|
||||||
|
struct QGBPrinter {
|
||||||
|
GBPrinter d;
|
||||||
|
CoreController* parent;
|
||||||
|
} m_printer;
|
||||||
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -233,6 +233,9 @@ bool MultiplayerController::attachGame(CoreController* controller) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void MultiplayerController::detachGame(CoreController* controller) {
|
void MultiplayerController::detachGame(CoreController* controller) {
|
||||||
|
if (m_players.empty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
mCoreThread* thread = controller->thread();
|
mCoreThread* thread = controller->thread();
|
||||||
if (!thread) {
|
if (!thread) {
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
|
@ -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
|
|
@ -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>
|
|
@ -41,6 +41,7 @@
|
||||||
#include "OverrideView.h"
|
#include "OverrideView.h"
|
||||||
#include "ObjView.h"
|
#include "ObjView.h"
|
||||||
#include "PaletteView.h"
|
#include "PaletteView.h"
|
||||||
|
#include "PrinterView.h"
|
||||||
#include "ROMInfo.h"
|
#include "ROMInfo.h"
|
||||||
#include "SensorView.h"
|
#include "SensorView.h"
|
||||||
#include "SettingsView.h"
|
#include "SettingsView.h"
|
||||||
|
@ -1358,9 +1359,7 @@ void Window::setupMenu(QMenuBar* menubar) {
|
||||||
fpsTargetOption->addValue(tr("240"), 240, target);
|
fpsTargetOption->addValue(tr("240"), 240, target);
|
||||||
m_config->updateOption("fpsTarget");
|
m_config->updateOption("fpsTarget");
|
||||||
|
|
||||||
#if defined(USE_PNG) || defined(USE_FFMPEG) || defined(USE_MAGICK)
|
|
||||||
avMenu->addSeparator();
|
avMenu->addSeparator();
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef USE_PNG
|
#ifdef USE_PNG
|
||||||
QAction* screenshot = new QAction(tr("Take &screenshot"), avMenu);
|
QAction* screenshot = new QAction(tr("Take &screenshot"), avMenu);
|
||||||
|
@ -1397,6 +1396,18 @@ void Window::setupMenu(QMenuBar* menubar) {
|
||||||
addControlledAction(avMenu, stopVL, "stopVL");
|
addControlledAction(avMenu, stopVL, "stopVL");
|
||||||
m_gameActions.append(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();
|
avMenu->addSeparator();
|
||||||
m_videoLayers = avMenu->addMenu(tr("Video layers"));
|
m_videoLayers = avMenu->addMenu(tr("Video layers"));
|
||||||
m_shortcutController->addMenu(m_videoLayers, avMenu);
|
m_shortcutController->addMenu(m_videoLayers, avMenu);
|
||||||
|
@ -1807,6 +1818,7 @@ void Window::setController(CoreController* controller, const QString& fname) {
|
||||||
}
|
}
|
||||||
|
|
||||||
m_controller->start();
|
m_controller->start();
|
||||||
|
m_controller->loadConfig(m_config);
|
||||||
}
|
}
|
||||||
|
|
||||||
WindowBackground::WindowBackground(QWidget* parent)
|
WindowBackground::WindowBackground(QWidget* parent)
|
||||||
|
|
Loading…
Reference in New Issue