From 1201b8928f21cd7e00f058dec7b2bf6cfb735565 Mon Sep 17 00:00:00 2001 From: Maximilian Mader Date: Sun, 3 Jan 2021 19:07:48 +0100 Subject: [PATCH] [GTK3] Add Game Boy Printer support --- Makefile | 2 +- gtk3/main.c | 69 +++++++- gtk3/printer_window.c | 213 ++++++++++++++++++++++++ gtk3/printer_window.h | 25 +++ gtk3/resources/ui/console_window.ui | 6 +- gtk3/resources/ui/preferences_window.ui | 6 +- gtk3/resources/ui/printer_window.ui | 103 ++++++++++++ gtk3/resources/ui/vram_viewer_window.ui | 6 +- gtk3/resources/ui/window.ui | 66 +------- gtk3/sameboy.gresource.xml | 6 +- gtk3/types.h | 3 +- 11 files changed, 422 insertions(+), 83 deletions(-) create mode 100644 gtk3/printer_window.c create mode 100644 gtk3/printer_window.h create mode 100644 gtk3/resources/ui/printer_window.ui diff --git a/Makefile b/Makefile index 5413d65..d180673 100644 --- a/Makefile +++ b/Makefile @@ -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 diff --git a/gtk3/main.c b/gtk3/main.c index 78e98fa..104e7ae 100644 --- a/gtk3/main.c +++ b/gtk3/main.c @@ -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; } diff --git a/gtk3/printer_window.c b/gtk3/printer_window.c new file mode 100644 index 0000000..0292a10 --- /dev/null +++ b/gtk3/printer_window.c @@ -0,0 +1,213 @@ +#include "printer_window.h" +#include +#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; + } +} diff --git a/gtk3/printer_window.h b/gtk3/printer_window.h new file mode 100644 index 0000000..4e9f542 --- /dev/null +++ b/gtk3/printer_window.h @@ -0,0 +1,25 @@ +#ifndef printer_window_h +#define printer_window_h + +#include +#include +#include + +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 diff --git a/gtk3/resources/ui/console_window.ui b/gtk3/resources/ui/console_window.ui index 92009a6..2e890d2 100644 --- a/gtk3/resources/ui/console_window.ui +++ b/gtk3/resources/ui/console_window.ui @@ -1,9 +1,9 @@ - - +