[GTK3] Add Game Boy Printer support

This commit is contained in:
Maximilian Mader 2021-01-03 19:07:48 +01:00
parent 39ad93210c
commit 1201b8928f
Signed by: Max
GPG Key ID: F71D56A3151C4FB3
11 changed files with 422 additions and 83 deletions

View File

@ -134,7 +134,7 @@ ifneq ($(findstring gtk3,$(MAKECMDGOALS)),)
$(error The gtk3 target requires pkg-config)
endif
else
GTK_OPTIONS := -DGTK_DISABLE_DEPRECATED=1 -DG_DISABLE_DEPRECATED=1 -DG_LOG_DOMAIN=\"SameBoy\" -DRESOURCE_PREFIX=\"/io/github/sameboy/\" -DAPP_ID=\"io.github.sameboy\"
GTK_OPTIONS := -DGDK_DISABLE_DEPRECATED=1 -DGTK_DISABLE_DEPRECATED=1 -DG_DISABLE_DEPRECATED=1 -DG_LOG_DOMAIN=\"SameBoy\" -DRESOURCE_PREFIX=\"/io/github/sameboy/\" -DAPP_ID=\"io.github.sameboy\"
ifeq ($(SDL_AUDIO_DRIVER),sdl)
GTK_OPTIONS += -DUSE_SDL_AUDIO

View File

@ -17,6 +17,7 @@
#include "console_window.h"
#include "preferences_window.h"
#include "vram_viewer_window.h"
#include "printer_window.h"
// used for audio and game controllers
#include "SDL.h"
@ -784,6 +785,11 @@ static void reset(void) {
GB_load_battery(&gb, gui_data.battery_save_path);
GB_load_cheats(&gb, gui_data.cheats_save_path);
size_t path_length = strlen(path);
char printer_suggestion_prefix[path_length];
replace_extension(path, path_length, printer_suggestion_prefix, "");
printer_window_set_suggestion_prefix(gui_data.printer, printer_suggestion_prefix);
GError *error = NULL;
GBytes *register_sym_f = g_resources_lookup_data(RESOURCE_PREFIX "Misc/registers.sym", G_RESOURCE_LOOKUP_FLAGS_NONE, &error);
if (register_sym_f) {
@ -793,7 +799,6 @@ static void reset(void) {
g_bytes_unref(register_sym_f);
}
size_t path_length = strlen(path);
char sym_file_path[path_length + 5];
replace_extension(path, path_length, sym_file_path, ".sym");
GB_debugger_load_symbol_file(&gb, sym_file_path);
@ -844,6 +849,24 @@ static void start(void) {
// Prevent dependency loop
static void run(void);
gpointer perform_atomic(gpointer (*fn)(gpointer args), gpointer args) {
while (!GB_is_inited(&gb));
bool was_running = gui_data.running && !GB_debugger_is_stopped(&gb);
if (was_running) {
stop();
}
// run the callback
gpointer ret_val = (*fn)(args);
if (was_running) {
run();
}
return ret_val;
}
// app.reset GAction
// Resets the emulation
static void activate_reset(GSimpleAction *action, GVariant *parameter, gpointer app) {
@ -1017,9 +1040,9 @@ static void startup(GApplication *app, gpointer null_ptr) {
gui_data.console = console_window_new(&gb);
gui_data.preferences = preferences_window_new(&gb);
gui_data.vram_viewer = vram_viewer_window_new();
gui_data.memory_viewer = GTK_WINDOW(get_object("memory_viewer"));
gui_data.printer = printer_window_new();
gui_data.printer = GTK_WINDOW(get_object("printer"));
gui_data.memory_viewer = GTK_WINDOW(get_object("memory_viewer"));
if (config.audio.sample_rate == -1) {
gui_data.sample_rate = GB_audio_default_sample_rate();
@ -1084,8 +1107,46 @@ G_MODULE_EXPORT void on_quit_activate(GtkWidget *w, gpointer user_data_ptr) {
quit();
}
static gboolean draw_printer_image(struct PrinterData *data) {
printer_window_update(gui_data.printer, data);
g_free(data->image);
g_slice_free(struct PrinterData, data);
return false;
}
static void print_image(GB_gameboy_t *gb, uint32_t *image, uint8_t height, uint8_t top_margin, uint8_t bottom_margin, uint8_t exposure) {
struct PrinterData *data = g_slice_alloc(sizeof(struct PrinterData));
data->image = g_malloc0(160 * height * sizeof(image[0]));
memcpy(data->image, image, 160 * height * sizeof(image[0]));
data->height = height;
data->top_margin = top_margin;
data->bottom_margin = bottom_margin;
data->exposure = exposure;
g_idle_add((GSourceFunc) draw_printer_image, data);
}
gpointer change_serial_device(gpointer ptr) {
gchar *device_id = ptr;
if (g_strcmp0(device_id, "NONE") == 0) {
g_debug("Disconnecting serial device");
GB_disconnect_serial(&gb);
}
else if (g_strcmp0(device_id, "PRINTER") == 0) {
g_debug("Connecting printer");
GB_connect_printer(&gb, print_image);
}
return NULL;
}
bool on_change_linked_device(GtkWidget *widget, gpointer user_data) {
GtkCheckMenuItem *check_menu_item = GTK_CHECK_MENU_ITEM(widget);
gchar *device_id = (gchar *) user_data;
if (!gtk_check_menu_item_get_active(check_menu_item)) {
return true;
@ -1094,7 +1155,7 @@ bool on_change_linked_device(GtkWidget *widget, gpointer user_data) {
return false;
}
g_message("Not yet implemented");
perform_atomic(change_serial_device, device_id);
return false;
}

213
gtk3/printer_window.c Normal file
View File

@ -0,0 +1,213 @@
#include "printer_window.h"
#include <stdbool.h>
#include "util.h"
struct _PrinterWindow {
GtkWindowClass parent_class;
GtkDrawingArea *printer_canvas;
GtkButton *printer_save_button;
GtkButton *printer_clear_button;
uint32_t *current_image;
size_t current_height;
cairo_surface_t *surface;
GMutex surface_mutex;
char *suggestion_prefix;
};
G_DEFINE_TYPE(PrinterWindow, printer_window, GTK_TYPE_WINDOW);
static gboolean on_printer_draw(GtkWidget *widget, cairo_t *cr, PrinterWindow *window) {
GtkStyleContext *context = gtk_widget_get_style_context(widget);
g_mutex_lock(&window->surface_mutex);
int width = cairo_image_surface_get_width(window->surface);
int height = cairo_image_surface_get_height(window->surface);
gtk_render_background(context, cr, 0, 0, width, height);
gtk_render_frame(context, cr, 0, 0, width, height);
cairo_scale(cr, 2.0, 2.0);
cairo_set_source_surface(cr, window->surface, 0, 0);
cairo_pattern_set_filter(cairo_get_source(cr), CAIRO_FILTER_NEAREST);
cairo_paint(cr);
g_mutex_unlock(&window->surface_mutex);
return false;
}
static void on_printer_save(GtkWidget *w, PrinterWindow *self) {
// This function is defined in `main.c` ...
gpointer perform_atomic(gpointer (*fn)(gpointer args), gpointer args);
// This is ugly, yup.
bool success = perform_atomic((gpointer)(*printer_window_save), self);
g_debug("File saving status: %d", success);
}
static void on_printer_clear(GtkWidget *w, PrinterWindow *self) {
printer_window_clear(self);
}
static void printer_window_init(PrinterWindow *window) {
gtk_widget_init_template(GTK_WIDGET(window));
set_combo_box_row_separator_func(GTK_CONTAINER(window));
printer_window_clear(window);
}
static void printer_window_finalize(GObject *object) {
PrinterWindow *window = (PrinterWindow *) object;
g_free(window->current_image);
cairo_surface_destroy(window->surface);
G_OBJECT_CLASS(printer_window_parent_class)->finalize(object);
}
static void printer_window_class_init(PrinterWindowClass *class) {
gtk_widget_class_set_template_from_resource(GTK_WIDGET_CLASS(class), RESOURCE_PREFIX "ui/printer_window.ui");
gtk_widget_class_bind_template_child(GTK_WIDGET_CLASS(class), PrinterWindow, printer_canvas);
gtk_widget_class_bind_template_child(GTK_WIDGET_CLASS(class), PrinterWindow, printer_save_button);
gtk_widget_class_bind_template_child(GTK_WIDGET_CLASS(class), PrinterWindow, printer_clear_button);
gtk_widget_class_bind_template_callback(GTK_WIDGET_CLASS(class), on_printer_draw);
gtk_widget_class_bind_template_callback(GTK_WIDGET_CLASS(class), on_printer_save);
gtk_widget_class_bind_template_callback(GTK_WIDGET_CLASS(class), on_printer_clear);
G_OBJECT_CLASS(class)->finalize = printer_window_finalize;
}
PrinterWindow *printer_window_new(void) {
return g_object_new(PRINTER_WINDOW_TYPE, NULL);
}
void printer_window_update(PrinterWindow *self, struct PrinterData *data) {
g_mutex_lock(&self->surface_mutex);
size_t current_size = self->current_height * 160;
size_t new_height = self->current_height
+ data->top_margin * 8
+ data->height
+ data->bottom_margin * 8;
size_t new_size = new_height * 160 * sizeof(uint32_t);
uint32_t *new_image = g_malloc(new_size);
memset(new_image, 0xFF, new_size); // fill with white
memcpy(new_image, self->current_image, current_size * sizeof(uint32_t)); // copy old image
memcpy(new_image + current_size + (data->top_margin * 160 * 8), data->image, data->height * 160 * sizeof(uint32_t)); // copy new image
g_free(self->current_image);
self->current_image = new_image;
self->current_height = new_height;
self->surface = cairo_image_surface_create_for_data(
(unsigned char*)self->current_image,
CAIRO_FORMAT_RGB24,
160,
self->current_height,
cairo_format_stride_for_width(CAIRO_FORMAT_RGB24, 160)
);
// cairo_surface_finish(self->surface);
g_mutex_unlock(&self->surface_mutex);
gtk_widget_set_size_request(GTK_WIDGET(self->printer_canvas), 160 * 2, self->current_height * 2);
gtk_window_present_with_time(GTK_WINDOW(self), time(NULL));
}
void printer_window_clear(PrinterWindow *self) {
g_mutex_lock(&self->surface_mutex);
self->surface = cairo_image_surface_create(CAIRO_FORMAT_RGB24, 160, 0);
self->current_image = NULL;
self->current_height = 0;
gtk_widget_set_size_request(GTK_WIDGET(self->printer_canvas), 160 * 2, 0);
g_mutex_unlock(&self->surface_mutex);
}
bool printer_window_save(PrinterWindow *self) {
GtkFileChooserNative *native = gtk_file_chooser_native_new("Save Printed Image", GTK_WINDOW(self), GTK_FILE_CHOOSER_ACTION_SAVE, "_Save", "_Cancel");
gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(native), true);
gtk_file_chooser_set_create_folders(GTK_FILE_CHOOSER(native), true);
if (self->suggestion_prefix == NULL) {
gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(native), "print.png");
}
else {
GString *suggestion = g_string_new(self->suggestion_prefix);
#if GLIB_CHECK_VERSION(2,62,0)
gchar *date = g_date_time_format_iso8601(g_date_time_new_now_local());
#else
GTimeVal tv;
g_get_current_time(&tv);
gchar *date = g_time_val_to_iso8601(&tv);
#endif
g_string_append_printf(suggestion, "_printout_%s.png", date);
for (gsize i = 0; i < suggestion->len; i++) {
if (suggestion->str[i] == ':') {
suggestion->str[i] = '-';
}
}
gchar *basename = g_path_get_basename(suggestion->str);
gchar *dirname = g_path_get_dirname(suggestion->str);
gtk_file_chooser_select_filename(GTK_FILE_CHOOSER(native), suggestion->str);
gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(native), basename);
g_free(dirname);
g_free(basename);
g_string_free(suggestion, true);
g_free(date);
}
GtkFileFilter *filter = gtk_file_filter_new();
gtk_file_filter_add_pattern(filter, "*.png");
gtk_file_filter_add_mime_type(filter, "image/png");
gtk_file_filter_set_name(filter, "PNG");
gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(native), filter);
gint res = gtk_native_dialog_run(GTK_NATIVE_DIALOG(native));
if (res == GTK_RESPONSE_ACCEPT) {
const char* path = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(native));
g_mutex_lock(&self->surface_mutex);
cairo_status_t status = cairo_surface_write_to_png(self->surface, path);
g_mutex_unlock(&self->surface_mutex);
return status == CAIRO_STATUS_SUCCESS;
}
g_object_unref(native);
return false;
}
void printer_window_set_suggestion_prefix(PrinterWindow *self, char* prefix) {
if (self->suggestion_prefix != NULL) {
g_free(self->suggestion_prefix);
}
if (prefix != NULL) {
self->suggestion_prefix = g_strdup(prefix);
}
else {
self->suggestion_prefix = NULL;
}
}

25
gtk3/printer_window.h Normal file
View File

@ -0,0 +1,25 @@
#ifndef printer_window_h
#define printer_window_h
#include <gtk/gtk.h>
#include <Core/gb.h>
#include <stdint.h>
struct PrinterData {
uint32_t *image;
uint8_t height;
uint8_t top_margin;
uint8_t bottom_margin;
uint8_t exposure;
};
#define PRINTER_WINDOW_TYPE (printer_window_get_type())
G_DECLARE_FINAL_TYPE(PrinterWindow, printer_window, SAMEBOY, PRINTER_WINDOW, GtkWindow)
PrinterWindow *printer_window_new(void);
void printer_window_clear(PrinterWindow *self);
bool printer_window_save(PrinterWindow *self);
void printer_window_update(PrinterWindow *self, struct PrinterData *data);
void printer_window_set_suggestion_prefix(PrinterWindow *self, char* prefix);
#endif

View File

@ -1,9 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.36.0
<!--
The MIT License (MIT)
Copyright (c) 2015-2019 Lior Halphon
Copyright (c) 2015-2021 Lior Halphon
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@ -31,7 +31,7 @@ Author: Maximilian Mader
<!-- interface-license-type mit -->
<!-- interface-name SameBoy -->
<!-- interface-description SameBoy is an open source Game Boy (DMG) and Game Boy Color (CGB) emulator, written in portable C. -->
<!-- interface-copyright 2015-2019 Lior Halphon -->
<!-- interface-copyright 2015-2021 Lior Halphon -->
<!-- interface-authors Maximilian Mader -->
<template class="ConsoleWindow">
<property name="can_focus">False</property>

View File

@ -1,9 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.36.0
<!--
The MIT License (MIT)
Copyright (c) 2015-2019 Lior Halphon
Copyright (c) 2015-2021 Lior Halphon
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@ -31,7 +31,7 @@ Author: Maximilian Mader
<!-- interface-license-type mit -->
<!-- interface-name SameBoy -->
<!-- interface-description SameBoy is an open source Game Boy (DMG) and Game Boy Color (CGB) emulator, written in portable C. -->
<!-- interface-copyright 2015-2019 Lior Halphon -->
<!-- interface-copyright 2015-2021 Lior Halphon -->
<!-- interface-authors Maximilian Mader -->
<template class="PreferencesWindow">
<property name="can_focus">False</property>

View File

@ -0,0 +1,103 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
The MIT License (MIT)
Copyright (c) 2015-2019 Lior Halphon
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.
Author: Maximilian Mader
-->
<interface>
<requires lib="gtk+" version="3.22"/>
<!-- interface-license-type mit -->
<!-- interface-name SameBoy -->
<!-- interface-description SameBoy is an open source Game Boy (DMG) and Game Boy Color (CGB) emulator, written in portable C. -->
<!-- interface-copyright 2015-2021 Lior Halphon -->
<!-- interface-authors Maximilian Mader -->
<template class="PrinterWindow">
<property name="can_focus">False</property>
<property name="resizable">False</property>
<property name="default_width">320</property>
<property name="default_height">432</property>
<child>
<object class="GtkScrolledWindow">
<property name="min-content-height">432</property>
<property name="max-content-height">432</property>
<property name="hexpand">0</property>
<property name="vexpand">1</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="shadow_type">in</property>
<child>
<object class="GtkViewport">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="shadow_type">none</property>
<child>
<object class="GtkDrawingArea" id="printer_canvas">
<property name="visible">True</property>
<property name="can_focus">False</property>
<signal name="draw" handler="on_printer_draw" swapped="no"/>
</object>
</child>
</object>
</child>
</object>
</child>
<child type="titlebar">
<object class="GtkHeaderBar">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="title" translatable="yes">Printer</property>
<property name="has_subtitle">False</property>
<property name="show_close_button">True</property>
<child>
<object class="GtkButton" id="printer_save_button">
<property name="label">gtk-save</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="use_stock">True</property>
<signal name="clicked" handler="on_printer_save" swapped="no"/>
</object>
<packing>
<property name="pack_type">end</property>
</packing>
</child>
<child>
<object class="GtkButton" id="printer_clear_button">
<property name="label">gtk-delete</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="use_stock">True</property>
<signal name="clicked" handler="on_printer_clear" swapped="no"/>
</object>
<packing>
<property name="pack_type">end</property>
<property name="position">1</property>
</packing>
</child>
</object>
</child>
</template>
</interface>

View File

@ -1,9 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.36.0
<!--
The MIT License (MIT)
Copyright (c) 2015-2019 Lior Halphon
Copyright (c) 2015-2021 Lior Halphon
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@ -31,7 +31,7 @@ Author: Maximilian Mader
<!-- interface-license-type mit -->
<!-- interface-name SameBoy -->
<!-- interface-description SameBoy is an open source Game Boy (DMG) and Game Boy Color (CGB) emulator, written in portable C. -->
<!-- interface-copyright 2015-2019 Lior Halphon -->
<!-- interface-copyright 2015-2021 Lior Halphon -->
<!-- interface-authors Maximilian Mader -->
<template class="VramViewerWindow">
<property name="can_focus">False</property>

View File

@ -1,9 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.36.0
<!--
The MIT License (MIT)
Copyright (c) 2015-2019 Lior Halphon
Copyright (c) 2015-2021 Lior Halphon
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@ -31,7 +31,7 @@ Author: Maximilian Mader
<!-- interface-license-type mit -->
<!-- interface-name SameBoy -->
<!-- interface-description SameBoy is an open source Game Boy (DMG) and Game Boy Color (CGB) emulator, written in portable C. -->
<!-- interface-copyright 2015-2019 Lior Halphon -->
<!-- interface-copyright 2015-2021 Lior Halphon -->
<!-- interface-authors Maximilian Mader -->
<object class="GtkAboutDialog" id="about_dialog">
<property name="can_focus">False</property>
@ -123,66 +123,6 @@ Maximilian Mader https://github.com/max-m</property>
<placeholder/>
</child>
</object>
<object class="GtkWindow" id="printer">
<property name="can_focus">False</property>
<property name="resizable">False</property>
<property name="default_width">320</property>
<property name="default_height">432</property>
<child>
<object class="GtkScrolledWindow">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="shadow_type">in</property>
<child>
<object class="GtkViewport">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="shadow_type">none</property>
<child>
<object class="GtkDrawingArea" id="printer_canvas">
<property name="visible">True</property>
<property name="can_focus">False</property>
</object>
</child>
</object>
</child>
</object>
</child>
<child type="titlebar">
<object class="GtkHeaderBar">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="title" translatable="yes">Printer</property>
<property name="has_subtitle">False</property>
<property name="show_close_button">True</property>
<child>
<object class="GtkButton" id="printer_save_button">
<property name="label">gtk-save</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="use_stock">True</property>
</object>
<packing>
<property name="pack_type">end</property>
</packing>
</child>
<child>
<object class="GtkButton" id="printer_clear_button">
<property name="label">gtk-delete</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="use_stock">True</property>
</object>
<packing>
<property name="pack_type">end</property>
<property name="position">1</property>
</packing>
</child>
</object>
</child>
</object>
<object class="GtkRecentFilter" id="recent_files_filter">
<mime-types>
<mime-type>application/x-gameboy-rom</mime-type>

View File

@ -5,11 +5,7 @@
<file preprocess="xml-stripblanks" compressed="true">ui/console_window.ui</file>
<file preprocess="xml-stripblanks" compressed="true">ui/preferences_window.ui</file>
<file preprocess="xml-stripblanks" compressed="true">ui/vram_viewer_window.ui</file>
<!--
<file preprocess="xml-stripblanks">gtk/menus.ui</file>
<file preprocess="xml-stripblanks">gtk/menus-common.ui</file>
<file preprocess="xml-stripblanks">gtk/menus-traditional.ui</file>
-->
<file preprocess="xml-stripblanks" compressed="true">ui/printer_window.ui</file>
<file compressed="true">css/main.css</file>
<file compressed="true" alias="gamecontrollerdb.txt">gamecontrollerdb_d3f1cea1.txt</file>

View File

@ -6,6 +6,7 @@
#include "console_window.h"
#include "preferences_window.h"
#include "vram_viewer_window.h"
#include "printer_window.h"
typedef struct{
int16_t x, y;
@ -45,9 +46,9 @@ typedef struct GuiData {
ConsoleWindow *console;
PreferencesWindow *preferences;
VramViewerWindow *vram_viewer;
PrinterWindow *printer;
GtkWindow *memory_viewer;
GtkWindow *printer;
// Audio and video
bool audio_initialized;