Compare commits

...

35 Commits

Author SHA1 Message Date
3b4f81ca78
Broken WIP 2019-10-06 01:13:45 +02:00
5d68673843
[GTK3] Run the emulation in its own thread
The code has not been checked for thread safety yet!
2019-10-05 21:09:30 +02:00
b23399c9ca
[GTK3] Implement VRAM tile information on hover 2019-10-05 16:21:53 +02:00
13d849cb41
[GTK3] Implement palette viewer 2019-10-05 15:40:48 +02:00
5020be359e
[GTK3] Implement sprite viewer 2019-10-04 01:40:50 +02:00
d75a261663
[GTK3] Handle VRAM viewer tilemap and -set options 2019-10-03 23:26:40 +02:00
6090bbf384
[GTK3] Make VRAM viewer button bar slimmer 2019-10-03 16:04:22 +02:00
6844ad31e8
[GTK3] Automatic resizing of the VRAM viewer 2019-10-03 15:26:49 +02:00
efb87f7a36
[GTK3] Rename "gtkget" macro "builder_get" 2019-10-03 03:13:53 +02:00
d9a9baae1b
[GTK3] Implement tilemap scrolling rect 2019-10-03 03:10:48 +02:00
e7cbc71fd6
[GTK3] Add function to draw grids in VRAM viewer 2019-10-02 23:53:18 +02:00
952934a3d7
[GTK3] Make GCC happier 2019-10-01 19:56:53 +02:00
714665f832
[GTK3] Make dummy preference callbacks functional 2019-09-30 22:30:33 +02:00
5476f45eb8
[GTK3] Use logical ARGB byte order for image buffers 2019-09-30 17:18:58 +02:00
be63e5b87d
[GTK3] Add main.h and re-order main.c 2019-09-30 16:40:55 +02:00
9614b47cd7
[GTK3] Allow multiple simultaneous instances 2019-09-30 03:41:00 +02:00
ade14530e4
[GTK3] Somewhat working GtkDrawingArea fallback 2019-09-30 01:49:41 +02:00
5641f26651
[GTK3] Fix a few problems with the settings 2019-09-29 02:07:33 +02:00
21adec8f30
[GTK3] Implement hard-coded input handling 2019-09-28 23:34:18 +02:00
8fb33900ec
[GTK3] Implement boot ROM search path preference 2019-09-28 00:15:37 +02:00
fd7370fc29
[GTK3] More work on preferences done 2019-09-27 23:10:28 +02:00
2d8aced75c
[GTK3] Handle config.boot_rom_path 2019-09-26 20:31:58 +02:00
ef682ec0c5
[GTK3] Make UI react to video config changes 2019-09-26 18:06:14 +02:00
f68472f1c3
[GTK3] Get color correction & highpass from config 2019-09-26 16:22:30 +02:00
eca563dfde
[GTK3] Prototype rendering of tilemap and tileset 2019-09-25 22:47:33 +02:00
d60df680bb
[GTK3] Save settings to key file 2019-09-25 22:47:04 +02:00
3709a88e6e
[GTK3] Load/save settings from/to file 2019-09-25 02:48:07 +02:00
64ac3690e7
[GTK3] Fix integer scaling 2019-09-24 20:22:07 +02:00
511d65fa45
[GTK3] Move config handling into settings.c 2019-09-24 16:15:50 +02:00
4caa1d0266
[GTK3] Add model and boot ROM CLI overrides 2019-09-24 01:04:59 +02:00
557bfd117e
[GTK3] Make rgb_encode() big endian compatible 2019-09-23 18:38:16 +02:00
56308f8b73
[GTK3] Stop the emulation when quitting 2019-09-23 16:35:10 +02:00
3c29a33043
[GTK3] Use frame buffering like the Cocoa frontend 2019-09-23 00:47:42 +02:00
2ea8858dfc
[GTK3] Implement OpenGL renderer prototype 2019-09-22 03:08:13 +02:00
1b2317e3c6
[GTK3] Add UI skeleton 2019-09-22 03:08:06 +02:00
26 changed files with 4599 additions and 9 deletions

8
.gitignore vendored
View File

@ -1 +1,9 @@
build
# intermediate source files generated at build time
gtk3/sameboy-gtk3-resources.c
gtk3/resources/gtk3/
# temporary backup file
*.*~

View File

@ -146,7 +146,7 @@ static void display_vblank(GB_gameboy_t *gb)
gb->rgb_encode_callback(gb, 0, 0, 0) :
gb->rgb_encode_callback(gb, 0xFF, 0xFF, 0xFF);
for (unsigned i = 0; i < WIDTH * LINES; i++) {
gb ->screen[i] = color;
gb->screen[i] = color;
}
}
}

View File

@ -1,5 +1,5 @@
# Make hacks
.INTERMEDIATE:
.INTERMEDIATE: gtk3/sameboy-gtk3-resources.c
# Set target, configuration, version and destination folders
@ -83,12 +83,19 @@ endif
CFLAGS += -Werror -Wall -Wno-unused-result -Wno-strict-aliasing -Wno-unknown-warning -Wno-unknown-warning-option -Wno-multichar -Wno-int-in-bool-context -std=gnu11 -D_GNU_SOURCE -DVERSION="$(VERSION)" -I. -D_USE_MATH_DEFINES
SDL_LDFLAGS := -lSDL2 -lGL
GTK3_CFLAGS := `pkg-config --cflags gio-2.0 gtk+-3.0 epoxy openal` -DGTK_DISABLE_DEPRECATED=1 -DG_DISABLE_DEPRECATED=1 -DRESOURCE_PREFIX=\"/io/github/sameboy/\" -DAPP_ID=\"io.github.sameboy\"
GTK3_LDFLAGS := `pkg-config --libs gio-2.0 gtk+-3.0 epoxy openal`
# TODO: REMOVE DISABLE UNUSED WARNINGS
GTK3_CFLAGS += -Wno-unused
ifeq ($(PLATFORM),windows32)
CFLAGS += -IWindows -Drandom=rand
LDFLAGS += -lmsvcrt -lcomdlg32 -lSDL2main -Wl,/MANIFESTFILE:NUL
SDL_LDFLAGS := -lSDL2 -lopengl32
else
LDFLAGS += -lc -lm -ldl
GTK3_LDFLAGS += -Wl -rdynamic
endif
ifeq ($(PLATFORM),Darwin)
@ -129,6 +136,7 @@ endif
cocoa: $(BIN)/SameBoy.app
quicklook: $(BIN)/SameBoy.qlgenerator
sdl: $(SDL_TARGET) $(BIN)/SDL/dmg_boot.bin $(BIN)/SDL/cgb_boot.bin $(BIN)/SDL/agb_boot.bin $(BIN)/SDL/sgb_boot.bin $(BIN)/SDL/sgb2_boot.bin $(BIN)/SDL/LICENSE $(BIN)/SDL/registers.sym $(BIN)/SDL/background.bmp $(BIN)/SDL/Shaders
gtk3: $(BIN)/gtk3/sameboy
bootroms: $(BIN)/BootROMs/agb_boot.bin $(BIN)/BootROMs/cgb_boot.bin $(BIN)/BootROMs/dmg_boot.bin $(BIN)/BootROMs/sgb_boot.bin $(BIN)/BootROMs/sgb2_boot.bin
tester: $(TESTER_TARGET) $(BIN)/tester/dmg_boot.bin $(BIN)/tester/cgb_boot.bin $(BIN)/tester/agb_boot.bin $(BIN)/tester/sgb_boot.bin $(BIN)/tester/sgb2_boot.bin
all: cocoa sdl tester libretro
@ -137,6 +145,7 @@ all: cocoa sdl tester libretro
CORE_SOURCES := $(shell ls Core/*.c)
SDL_SOURCES := $(shell ls SDL/*.c) $(OPEN_DIALOG)
GTK3_SOURCES := $(shell ls gtk3/*.c) gtk3/sameboy-gtk3-resources.c
TESTER_SOURCES := $(shell ls Tester/*.c)
ifeq ($(PLATFORM),Darwin)
@ -152,6 +161,7 @@ CORE_OBJECTS := $(patsubst %,$(OBJ)/%.o,$(CORE_SOURCES))
COCOA_OBJECTS := $(patsubst %,$(OBJ)/%.o,$(COCOA_SOURCES))
QUICKLOOK_OBJECTS := $(patsubst %,$(OBJ)/%.o,$(QUICKLOOK_SOURCES))
SDL_OBJECTS := $(patsubst %,$(OBJ)/%.o,$(SDL_SOURCES))
GTK3_OBJECTS := $(patsubst %,$(OBJ)/%.o,$(GTK3_SOURCES))
TESTER_OBJECTS := $(patsubst %,$(OBJ)/%.o,$(TESTER_SOURCES))
# Automatic dependency generation
@ -161,6 +171,9 @@ ifneq ($(filter-out clean bootroms libretro %.bin, $(MAKECMDGOALS)),)
ifneq ($(filter $(MAKECMDGOALS),sdl),)
-include $(SDL_OBJECTS:.o=.dep)
endif
ifneq ($(filter $(MAKECMDGOALS),gtk3),)
-include $(GTK3_OBJECTS:.o=.gtk3dep)
endif
ifneq ($(filter $(MAKECMDGOALS),tester),)
-include $(TESTER_OBJECTS:.o=.dep)
endif
@ -169,6 +182,16 @@ ifneq ($(filter $(MAKECMDGOALS),cocoa),)
endif
endif
gtk3/sameboy-gtk3-resources.c: gtk3/sameboy.gresource.xml $(shell glib-compile-resources --sourcedir=gtk3/resources --generate-dependencies gtk3/sameboy.gresource.xml)
glib-compile-resources --sourcedir=gtk3/resources --target=$@ --generate-source $<
# Resource hack cleanup
rm -rf gtk3/resources/gtk3
# GTK3 requires special CFLAGS
$(OBJ)/gtk3/%.gtk3dep: %
-@$(MKDIR) -p $(dir $@)
$(CC) $(CFLAGS) $(GTK3_CFLAGS) -MT $(OBJ)/$^.o -M $^ -c -o $@
$(OBJ)/%.dep: %
-@$(MKDIR) -p $(dir $@)
$(CC) $(CFLAGS) -MT $(OBJ)/$^.o -M $^ -c -o $@
@ -179,6 +202,11 @@ $(OBJ)/Core/%.c.o: Core/%.c
-@$(MKDIR) -p $(dir $@)
$(CC) $(CFLAGS) -DGB_INTERNAL -c $< -o $@
# GTK3 requires special CFLAGS
$(OBJ)/gtk3/%.c.o: gtk3/%.c
-@$(MKDIR) -p $(dir $@)
$(CC) $(CFLAGS) $(GTK3_CFLAGS) -c $< -o $@
$(OBJ)/%.c.o: %.c
-@$(MKDIR) -p $(dir $@)
$(CC) $(CFLAGS) -c $< -o $@
@ -261,7 +289,7 @@ ifeq ($(CONF), release)
strip $@
endif
# Windows version builds two, one with a conole and one without it
# Windows version builds two, one with a console and one without it
$(BIN)/SDL/sameboy.exe: $(CORE_OBJECTS) $(SDL_OBJECTS) $(OBJ)/Windows/resources.o
-@$(MKDIR) -p $(dir $@)
$(CC) $^ -o $@ $(LDFLAGS) $(SDL_LDFLAGS) -Wl,/subsystem:windows
@ -288,6 +316,15 @@ $(BIN)/SDL/SDL2.dll:
@$(eval MATCH := $(shell where $$LIB:SDL2.dll))
cp "$(MATCH)" $@
# GTK3
$(BIN)/gtk3/sameboy: $(CORE_OBJECTS) $(GTK3_OBJECTS)
-@$(MKDIR) -p $(dir $@)
$(CC) $^ -o $@ $(LDFLAGS) $(GTK3_LDFLAGS)
ifeq ($(CONF), release)
strip $@
endif
# Tester
$(BIN)/tester/sameboy_tester: $(CORE_OBJECTS) $(TESTER_OBJECTS)
@ -325,6 +362,15 @@ $(BIN)/SDL/Shaders: Shaders
-@$(MKDIR) -p $@
cp -rf Shaders/*.fsh $@
# Fugly resource hack
gtk3/resources/bootroms/%.bin: $(BOOTROMS_DIR)/%.bin
-@$(MKDIR) -p gtk3/resources/gtk3/resources/bootroms/
cp -f $< gtk3/resources/$@
gtk3/resources/Shaders/%.fsh: Shaders/%.fsh
-@$(MKDIR) -p gtk3/resources/gtk3/resources/Shaders/
cp -rf $< gtk3/resources/$@
# Boot ROMs
$(OBJ)/%.1bpp: %.png

10
gtk3/macros.h Normal file
View File

@ -0,0 +1,10 @@
#ifndef macros_h
#define macros_h
#define str(x) #x
#define xstr(x) str(x)
#define get_object(id) gtk_builder_get_object(builder, id)
#define builder_get(type, id) type(gtk_builder_get_object(builder, id))
#define itoa(val) g_strdup_printf("%i", val)
#endif /* macros_h */

1584
gtk3/main.c Normal file

File diff suppressed because it is too large Load Diff

164
gtk3/main.h Normal file
View File

@ -0,0 +1,164 @@
#ifndef main_h
#define main_h
#include <AL/al.h>
#include <AL/alc.h>
#include <gtk/gtk.h>
#include <epoxy/gl.h>
#include <signal.h>
#include <stdbool.h>
#include <stdio.h>
#include <string.h>
#include <Core/gb.h>
#include "macros.h"
#include "settings.h"
#include "shader.h"
#define OPENAL_ERROR openal_error_quark()
GQuark openal_error_quark (void) {
return g_quark_from_static_string("openal-error-quark");
}
GError *openal_error(const gchar *user_msg) {
ALCenum error = alGetError();
gchar *description;
gchar *msg;
switch (error) {
case AL_NO_ERROR:
return NULL;
case AL_INVALID_NAME:
description = "A bad name (ID) was passed to an OpenAL function";
break;
case AL_INVALID_ENUM:
description = "An invalid enum value was passed to an OpenAL function";
break;
case AL_INVALID_VALUE:
description = "An invalid value was passed to an OpenAL function";
break;
case AL_INVALID_OPERATION:
description = "The requested operation is not valid";
break;
case AL_OUT_OF_MEMORY:
description = "The requested operation resulted in OpenAL running out of memory";
break;
}
if (user_msg != NULL) {
msg = g_strdup_printf("%s: %s", user_msg, description);
}
else {
msg = description;
}
return g_error_new_literal(OPENAL_ERROR, error, msg);
}
typedef struct UserData {
bool fullscreen;
GFile *file;
gchar *boot_rom_path;
gchar *config_path;
GB_model_t model;
} UserData;
typedef struct{
int16_t x, y;
uint16_t w, h;
} Rect;
#define BUTTON_MASK_A 0x01
#define BUTTON_MASK_B 0x02
#define BUTTON_MASK_START 0x04
#define BUTTON_MASK_SELECT 0x08
#define BUTTON_MASK_UP 0x10
#define BUTTON_MASK_DOWN 0x20
#define BUTTON_MASK_LEFT 0x40
#define BUTTON_MASK_RIGHT 0x80
int main(int argc, char *argv[]);
static gint handle_local_options(GApplication *app, GVariantDict *options, gpointer user_data_gptr);
// GtkGlArea crash workaround
void gl_check_realize(GtkWidget *w, gpointer user_data_gptr);
gboolean test_gl_support(void);
static void setup_audio();
static GMenuModel *get_menu_model(GApplication *app, const char *id);
static void create_fallback_canvas(void);
static void setup_menu(GApplication *app);
static void set_combo_box_row_separator_func(GtkContainer *container);
static gboolean is_separator(GtkTreeModel *model, GtkTreeIter *iter, gpointer data);
// Rendering support functions
static unsigned char number_of_buffers(void);
static uint32_t *get_pixels(void);
static uint32_t *get_current_buffer(void);
static uint32_t *get_previous_buffer(void);
static void flip(void);
// GApplication signals
static void startup(GApplication *app, gpointer user_data_gptr);
static void activate(GApplication *app, gpointer user_data_gptr);
static void shutdown(GApplication *app, GFile **files, gint n_files, const gchar *hint, gpointer user_data_gptr);
static void open(GApplication *app, GFile **files, gint n_files, const gchar *hint, gpointer user_data_gptr);
static void quit(GApplication *app);
static gboolean on_key_press(GtkWidget *w, GdkEventKey *event, gpointer data);
// App actions
static void activate_about(GSimpleAction *action, GVariant *parameter, gpointer app);
static void activate_open_gtk_debugger(GSimpleAction *action, GVariant *parameter, gpointer app);
static void activate_open_memory_viewer(GSimpleAction *action, GVariant *parameter, gpointer app);
static void activate_open_vram_viewer(GSimpleAction *action, GVariant *parameter, gpointer app);
static void activate_open(GSimpleAction *action, GVariant *parameter, gpointer app);
static void activate_preferences(GSimpleAction *action, GVariant *parameter, gpointer app);
static void activate_quit(GSimpleAction *action, GVariant *parameter, gpointer app);
// Signal callback
static void on_quit(GtkWidget *w, gpointer app);
// Renderer area bindings
static void gl_init(GtkWidget *w);
static void gl_draw();
static void gl_finish();
static gboolean on_draw_fallback(GtkWidget *widget, cairo_t *cr, gpointer data);
static void resize();
// VRAM viewer bindings
static void on_vram_viewer_realize();
static void on_vram_viewer_unrealize();
static gboolean on_draw_vram_viewer_tilemap(GtkWidget *widget, cairo_t *cr, gpointer data);
static gboolean on_draw_vram_viewer_tileset(GtkWidget *widget, cairo_t *cr, gpointer data);
static gboolean on_motion_vram_viewer_tileset(GtkWidget *widget, GdkEventMotion *event);
static gboolean on_motion_vram_viewer_tilemap(GtkWidget *widget, GdkEventMotion *event);
static void on_vram_tab_change(GtkWidget *widget, GParamSpec *pspec, GtkStackSwitcher *self);
// Option bindings
G_MODULE_EXPORT void on_boot_rom_location_changed(GtkWidget *w, gpointer user_data_gptr);
G_MODULE_EXPORT void on_cgb_model_changed(GtkWidget *w, gpointer user_data_gptr);
G_MODULE_EXPORT void on_color_correction_changed(GtkWidget *w, gpointer user_data_gptr);
G_MODULE_EXPORT void on_color_menubar_override_changed(GtkWidget *w, gpointer user_data_gptr);
G_MODULE_EXPORT void on_dmg_model_changed(GtkWidget *w, gpointer user_data_gptr);
G_MODULE_EXPORT void on_graphic_filter_changed(GtkWidget *w, gpointer user_data_gptr);
G_MODULE_EXPORT void on_highpass_filter_changed(GtkWidget *w, gpointer user_data_gptr);
G_MODULE_EXPORT void on_keep_aspect_ratio_changed(GtkWidget *w, gpointer user_data_gptr);
G_MODULE_EXPORT void on_rewind_duration_changed(GtkWidget *w, gpointer user_data_gptr);
G_MODULE_EXPORT void on_sgb_model_changed(GtkWidget *w, gpointer user_data_gptr);
G_MODULE_EXPORT void on_use_integer_scaling_changed(GtkWidget *w, gpointer user_data_gptr);
static uint32_t rgb_encode(GB_gameboy_t *gb, uint8_t r, uint8_t g, uint8_t b);
static void render_texture(void *pixels, void *previous);
static void update_viewport(void);
static void update_window_geometry();
static void handle_events(GB_gameboy_t *gb);
static uint32_t convert_color(uint16_t color);
static void palette_color_data_func(GtkTreeViewColumn *col, GtkCellRenderer *renderer, GtkTreeModel *model, GtkTreeIter *iter, gpointer user_data);
static void vblank(GB_gameboy_t *gb);
static gpointer run(gpointer user_data_gptr);
#endif /* main_h */

View File

@ -0,0 +1,10 @@
.vram-viewer-button-bar {
margin-bottom: 4px;
}
.vram-viewer-button-bar button {
padding: 2px 4px;
min-height: 11px;
min-width: 12px;
font-size: 0.8rem;
}

View File

@ -0,0 +1,431 @@
<?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>
<!-- interface-requires gtk+ 3.0 -->
<!-- menubar is in common since on ubuntu would be picked from menus-traditional, and on osx from menus -->
<menu id="menubar">
<section>
<submenu>
<attribute name="label" translatable="yes">_SameBoy</attribute>
<section>
<attribute name="id">sameboy-section-0</attribute>
<item>
<attribute name="label" translatable="yes">About SameBoy</attribute>
<attribute name="action">app.about</attribute>
</item>
<item>
<attribute name="label" translatable="yes">Preferences</attribute>
<attribute name="action">app.preferences</attribute>
</item>
</section>
<section>
<attribute name="id">sameboy-section-1</attribute>
<item>
<attribute name="label" translatable="yes">Hide SameBoy</attribute>
<attribute name="action">app.hide_app</attribute>
</item>
<item>
<attribute name="label" translatable="yes">Hide Others</attribute>
<attribute name="action">app.hide_others</attribute>
</item>
<item>
<attribute name="label" translatable="yes">Show All</attribute>
<attribute name="action">app.show_all</attribute>
</item>
<item>
<attribute name="label" translatable="yes">Quit SameBoy</attribute>
<attribute name="action">app.quit</attribute>
</item>
</section>
</submenu>
<submenu>
<attribute name="label" translatable="yes">_File</attribute>
<section>
<attribute name="id">file-section-0</attribute>
<item>
<attribute name="label" translatable="yes">_Open</attribute>
<attribute name="action">app.open</attribute>
</item>
<submenu>
<attribute name="label" translatable="yes">Open _Recent</attribute>
<section>
<attribute name="id">recent-files-section</attribute>
</section>
</submenu>
</section>
<section>
<attribute name="id">file-section-1</attribute>
<item>
<attribute name="label" translatable="yes">Close</attribute>
<attribute name="action">app.close</attribute>
</item>
</section>
</submenu>
<submenu>
<attribute name="label" translatable="yes">_Edit</attribute>
<section>
<attribute name="id">edit-section-0</attribute>
<item>
<attribute name="label" translatable="yes">Undo</attribute>
<attribute name="action">app.undo</attribute>
</item>
<item>
<attribute name="label" translatable="yes">Redo</attribute>
<attribute name="action">app.redo</attribute>
</item>
</section>
<section>
<attribute name="id">edit-section-1</attribute>
<item>
<attribute name="label" translatable="yes">Cut</attribute>
<attribute name="action">app.cut</attribute>
</item>
<item>
<attribute name="label" translatable="yes">Copy</attribute>
<attribute name="action">app.copy</attribute>
</item>
<item>
<attribute name="label" translatable="yes">Paste</attribute>
<attribute name="action">app.paste</attribute>
</item>
<item>
<attribute name="label" translatable="yes">Delete</attribute>
<attribute name="action">app.delete</attribute>
</item>
<item>
<attribute name="label" translatable="yes">Select All</attribute>
<attribute name="action">app.select_all</attribute>
</item>
</section>
<section>
<submenu>
<attribute name="label" translatable="yes">Find</attribute>
<section>
<attribute name="id">find-section-0</attribute>
<item>
<attribute name="label" translatable="yes">Find…</attribute>
<attribute name="action">app.find</attribute>
</item>
<item>
<attribute name="label" translatable="yes">Find Next</attribute>
<attribute name="action">app.find_next</attribute>
</item>
<item>
<attribute name="label" translatable="yes">Find Previous</attribute>
<attribute name="action">app.find_previous</attribute>
</item>
<item>
<attribute name="label" translatable="yes">Use Selection for Find</attribute>
<attribute name="action">app.find_selection</attribute>
</item>
<item>
<attribute name="label" translatable="yes">Jump to Selection</attribute>
<attribute name="action">app.jump_selection</attribute>
</item>
</section>
</submenu>
</section>
</submenu>
<submenu>
<attribute name="label" translatable="yes">E_mulation</attribute>
<section>
<attribute name="id">emulation-section-0</attribute>
<item>
<attribute name="label" translatable="yes">Reset</attribute>
<attribute name="action">app.reset</attribute>
</item>
<item>
<attribute name="label" translatable="yes">Pause</attribute>
<attribute name="action">app.pause</attribute>
</item>
</section>
<section>
<attribute name="id">emulation-section-1</attribute>
<submenu>
<attribute name="label" translatable="yes">Save State</attribute>
<item>
<attribute name="label" translatable="yes">Slot 1</attribute>
<attribute name="save-slot">0</attribute>
<attribute name="action">app.save_state</attribute>
</item>
<item>
<attribute name="label" translatable="yes">Slot 2</attribute>
<attribute name="save-slot">1</attribute>
<attribute name="action">app.save_state</attribute>
</item>
<item>
<attribute name="label" translatable="yes">Slot 3</attribute>
<attribute name="save-slot">2</attribute>
<attribute name="action">app.save_state</attribute>
</item>
<item>
<attribute name="label" translatable="yes">Slot 4</attribute>
<attribute name="save-slot">3</attribute>
<attribute name="action">app.save_state</attribute>
</item>
<item>
<attribute name="label" translatable="yes">Slot 5</attribute>
<attribute name="save-slot">4</attribute>
<attribute name="action">app.save_state</attribute>
</item>
<item>
<attribute name="label" translatable="yes">Slot 6</attribute>
<attribute name="save-slot">5</attribute>
<attribute name="action">app.save_state</attribute>
</item>
<item>
<attribute name="label" translatable="yes">Slot 7</attribute>
<attribute name="save-slot">6</attribute>
<attribute name="action">app.save_state</attribute>
</item>
<item>
<attribute name="label" translatable="yes">Slot 8</attribute>
<attribute name="save-slot">7</attribute>
<attribute name="action">app.save_state</attribute>
</item>
<item>
<attribute name="label" translatable="yes">Slot 9</attribute>
<attribute name="save-slot">8</attribute>
<attribute name="action">app.save_state</attribute>
</item>
<item>
<attribute name="label" translatable="yes">Slot 10</attribute>
<attribute name="save-slot">9</attribute>
<attribute name="action">app.save_state</attribute>
</item>
</submenu>
<submenu>
<attribute name="label" translatable="yes">Save State</attribute>
<item>
<attribute name="label" translatable="yes">Slot 1</attribute>
<attribute name="save-slot">0</attribute>
<attribute name="action">app.load_state</attribute>
</item>
<item>
<attribute name="label" translatable="yes">Slot 2</attribute>
<attribute name="save-slot">1</attribute>
<attribute name="action">app.load_state</attribute>
</item>
<item>
<attribute name="label" translatable="yes">Slot 3</attribute>
<attribute name="save-slot">2</attribute>
<attribute name="action">app.load_state</attribute>
</item>
<item>
<attribute name="label" translatable="yes">Slot 4</attribute>
<attribute name="save-slot">3</attribute>
<attribute name="action">app.load_state</attribute>
</item>
<item>
<attribute name="label" translatable="yes">Slot 5</attribute>
<attribute name="save-slot">4</attribute>
<attribute name="action">app.load_state</attribute>
</item>
<item>
<attribute name="label" translatable="yes">Slot 6</attribute>
<attribute name="save-slot">5</attribute>
<attribute name="action">app.load_state</attribute>
</item>
<item>
<attribute name="label" translatable="yes">Slot 7</attribute>
<attribute name="save-slot">6</attribute>
<attribute name="action">app.load_state</attribute>
</item>
<item>
<attribute name="label" translatable="yes">Slot 8</attribute>
<attribute name="save-slot">7</attribute>
<attribute name="action">app.load_state</attribute>
</item>
<item>
<attribute name="label" translatable="yes">Slot 9</attribute>
<attribute name="save-slot">8</attribute>
<attribute name="action">app.load_state</attribute>
</item>
<item>
<attribute name="label" translatable="yes">Slot 10</attribute>
<attribute name="save-slot">9</attribute>
<attribute name="action">app.load_state</attribute>
</item>
</submenu>
</section>
<section>
<attribute name="id">emulation-section-2</attribute>
<item>
<attribute name="label" translatable="yes">Game Boy</attribute>
<attribute name="action">app.change_model</attribute>
<attribute name="model">DMG</attribute>
</item>
<item>
<attribute name="label" translatable="yes">Super Game Boy</attribute>
<attribute name="action">app.change_model</attribute>
<attribute name="model">SGB</attribute>
</item>
<item>
<attribute name="label" translatable="yes">Game Boy Color</attribute>
<attribute name="action">app.change_model</attribute>
<attribute name="model">CGB</attribute>
</item>
<item>
<attribute name="label" translatable="yes">Game Boy Advance</attribute>
<attribute name="action">app.change_model</attribute>
<attribute name="model">AGB</attribute>
</item>
</section>
<section>
<attribute name="id">emulation-section-3</attribute>
<item>
<attribute name="label" translatable="yes">Mute Sound</attribute>
<attribute name="action">app.toggle_mute</attribute>
</item>
</section>
<section>
<attribute name="id">emulation-section-4</attribute>
<item>
<attribute name="label" translatable="yes">Blend Frames</attribute>
<attribute name="action">app.toggle_blend_frames</attribute>
</item>
</section>
</submenu>
<submenu>
<attribute name="label" translatable="yes">_Connectivity</attribute>
<section>
<attribute name="id">connectivity-section-0</attribute>
<item>
<attribute name="label" translatable="yes">None</attribute>
<attribute name="action">app.change_serial_device</attribute>
<attribute name="serial-device">none</attribute>
</item>
<item>
<attribute name="label" translatable="yes">Game Boy Printer</attribute>
<attribute name="action">app.change_serial_device</attribute>
<attribute name="serial-device">gb_printer</attribute>
</item>
</section>
</submenu>
<submenu>
<attribute name="label" translatable="yes">_Developer</attribute>
<section>
<attribute name="id">developer-section-0</attribute>
<item>
<attribute name="label" translatable="yes">Developer Mode</attribute>
<attribute name="action">app.toggle_developer_mode</attribute>
</item>
</section>
<section>
<attribute name="id">developer-section-1</attribute>
<item>
<attribute name="label" translatable="yes">Show Console</attribute>
<attribute name="action">app.show_console</attribute>
</item>
<item>
<attribute name="label" translatable="yes">Clear Console</attribute>
<attribute name="action">app.clear_console</attribute>
</item>
</section>
<section>
<attribute name="id">developer-section-2</attribute>
<item>
<attribute name="label" translatable="yes">Break Debugger</attribute>
<attribute name="action">app.break_debugger</attribute>
</item>
</section>
<section>
<attribute name="id">developer-section-3</attribute>
<item>
<attribute name="label" translatable="yes">Show Memory Viewer</attribute>
<attribute name="action">app.open_memory_viewer</attribute>
</item>
<item>
<attribute name="label" translatable="yes">Show VRAM Viewer</attribute>
<attribute name="action">app.open_vram_viewer</attribute>
</item>
</section>
<section>
<attribute name="id">developer-section-4</attribute>
<item>
<attribute name="label" translatable="yes">Show GTK Debugger</attribute>
<attribute name="action">app.open_gtk_debugger</attribute>
<attribute name="hidden-when">action-disabled</attribute>
</item>
</section>
</submenu>
<submenu>
<attribute name="label" translatable="yes">_Window</attribute>
<section>
<attribute name="id">window-section-0</attribute>
<item>
<attribute name="label" translatable="yes">Minimize</attribute>
<attribute name="action">app.minimize</attribute>
</item>
<item>
<attribute name="label" translatable="yes">Zoom</attribute>
<attribute name="action">app.zoom</attribute>
</item>
</section>
<section>
<attribute name="id">window-section-1</attribute>
<item>
<attribute name="label" translatable="yes">Bring All to Front</attribute>
<attribute name="action">app.bring_to_front</attribute>
</item>
</section>
</submenu>
<submenu>
<attribute name="label" translatable="yes">_Help</attribute>
<section>
<attribute name="id">help-section-0</attribute>
<item>
<attribute name="label" translatable="yes">SameBoy Help</attribute>
<attribute name="action">app.help</attribute>
</item>
</section>
</submenu>
</section>
</menu>
</interface>

View File

@ -0,0 +1,30 @@
<?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>
<!-- interface-requires gtk+ 3.0 -->
</interface>

View File

@ -0,0 +1,30 @@
<?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>
<!-- interface-requires gtk+ 3.0 -->
</interface>

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 974 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

1600
gtk3/resources/ui/window.ui Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,48 @@
<?xml version="1.0" encoding="UTF-8"?>
<gresources>
<gresource prefix="/io/github/sameboy">
<file preprocess="xml-stripblanks" compressed="true">ui/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 compressed="true">css/main.css</file>
<file alias="logo_256.png">pixmaps/logo/logo_256.png</file>
<file alias="logo_128.png">pixmaps/logo/logo_128.png</file>
<file alias="logo_64.png">pixmaps/logo/logo_64.png</file>
<file alias="logo_48.png">pixmaps/logo/logo_48.png</file>
<file alias="logo_32.png">pixmaps/logo/logo_32.png</file>
<file alias="logo_16.png">pixmaps/logo/logo_16.png</file>
<!-- Boot ROMs, generated at compile time -->
<!-- The file path is a hack to get automatic dependency generation working-->
<file compressed="true" alias="bootroms/dmg_boot.bin">gtk3/resources/bootroms/dmg_boot.bin</file>
<file compressed="true" alias="bootroms/cgb_boot.bin">gtk3/resources/bootroms/cgb_boot.bin</file>
<file compressed="true" alias="bootroms/agb_boot.bin">gtk3/resources/bootroms/agb_boot.bin</file>
<file compressed="true" alias="bootroms/sgb_boot.bin">gtk3/resources/bootroms/sgb_boot.bin</file>
<file compressed="true" alias="bootroms/sgb2_boot.bin">gtk3/resources/bootroms/sgb2_boot.bin</file>
<!-- Shaders -->
<!-- The file path is a hack to get automatic dependency generation working-->
<file compressed="true" alias="Shaders/AAOmniScaleLegacy.fsh">gtk3/resources/Shaders/AAOmniScaleLegacy.fsh</file>
<file compressed="true" alias="Shaders/AAScale2x.fsh">gtk3/resources/Shaders/AAScale2x.fsh</file>
<file compressed="true" alias="Shaders/AAScale4x.fsh">gtk3/resources/Shaders/AAScale4x.fsh</file>
<file compressed="true" alias="Shaders/Bilinear.fsh">gtk3/resources/Shaders/Bilinear.fsh</file>
<file compressed="true" alias="Shaders/CRT.fsh">gtk3/resources/Shaders/CRT.fsh</file>
<file compressed="true" alias="Shaders/HQ2x.fsh">gtk3/resources/Shaders/HQ2x.fsh</file>
<file compressed="true" alias="Shaders/LCD.fsh">gtk3/resources/Shaders/LCD.fsh</file>
<file compressed="true" alias="Shaders/MasterShader.fsh">gtk3/resources/Shaders/MasterShader.fsh</file>
<file compressed="true" alias="Shaders/NearestNeighbor.fsh">gtk3/resources/Shaders/NearestNeighbor.fsh</file>
<file compressed="true" alias="Shaders/OmniScale.fsh">gtk3/resources/Shaders/OmniScale.fsh</file>
<file compressed="true" alias="Shaders/OmniScaleLegacy.fsh">gtk3/resources/Shaders/OmniScaleLegacy.fsh</file>
<file compressed="true" alias="Shaders/Scale2x.fsh">gtk3/resources/Shaders/Scale2x.fsh</file>
<file compressed="true" alias="Shaders/Scale4x.fsh">gtk3/resources/Shaders/Scale4x.fsh</file>
<file compressed="true" alias="Shaders/SmoothBilinear.fsh">gtk3/resources/Shaders/SmoothBilinear.fsh</file>
<file>pixmaps/CPU.png</file>
<file>pixmaps/Display.png</file>
<file>pixmaps/Joypad.png</file>
<file>pixmaps/Speaker.png</file>
</gresource>
</gresources>

301
gtk3/settings.c Normal file
View File

@ -0,0 +1,301 @@
#include "settings.h"
static void print_config_error(GError *error) {
if (error == NULL) return;
if (!g_error_matches(error, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_KEY_NOT_FOUND) && !g_error_matches(error, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_GROUP_NOT_FOUND)) {
g_printerr("Config error: %s\n", error->message);
}
}
void print_config(config_t *config) {
#define EXPAND_GROUP(group_name, members) \
g_print("[%s]\n", #group_name); \
members
#define EXPAND_GROUP_MEMBER(member, key_type, default_value) \
g_print("%s="FORMAT_FOR_KEY_TYPE(key_type)"\n", #member, config->member);
EXPAND_CONFIG
#undef EXPAND_GROUP
#undef EXPAND_GROUP_MEMBER
}
void load_config_from_key_file(config_t *config, GKeyFile *key_file) {
g_print("Loading config from key file\n");
GError *error = NULL;
gchar *group_name;
#define EXPAND_GROUP(name, members) \
group_name = #name; \
members
#define EXPAND_GROUP_MEMBER(member, key_type, default_value) \
config->member = g_key_file_get_##key_type(key_file, group_name, #member, &error); \
if (error != NULL) { \
config->member = default_value; \
print_config_error(error); \
g_clear_error(&error); \
}
EXPAND_CONFIG
if (config->rewind_duration > 600) {
g_warning("Setting Emulation.rewind_duration too high might affect performance.\n");
}
#undef EXPAND_GROUP
#undef EXPAND_GROUP_MEMBER
}
void save_config_to_key_file(config_t *config, GKeyFile *key_file) {
g_print("Saving config to key file\n");
GError *error = NULL;
gchar *group_name;
#define EXPAND_GROUP(name, members) \
group_name = #name; \
members
#define EXPAND_GROUP_MEMBER_IF_0(member, key_type, default_value) \
g_key_file_set_##key_type(key_file, group_name, #member, config->member);
#define EXPAND_GROUP_MEMBER_IF_1(member, key_type, default_value) \
if (config->member != NULL) { \
g_key_file_set_##key_type(key_file, group_name, #member, config->member); \
} \
else if (g_key_file_has_key(key_file, group_name, #member, &error)) { \
if (error != NULL) { \
g_printerr("%s\n", error->message); \
g_clear_error(&error); \
} \
g_key_file_remove_key(key_file, group_name, #member, &error); \
if (error != NULL) { \
g_printerr("%s\n", error->message); \
g_clear_error(&error); \
} \
}
#define EXPAND_GROUP_MEMBER_IF_EVAL(y, member, key_type, default_value) EXPAND_GROUP_MEMBER_IF_ ## y(member, key_type, default_value)
#define EXPAND_GROUP_MEMBER_IF(member, key_type, is_pointer, default_value) EXPAND_GROUP_MEMBER_IF_EVAL(is_pointer, member, key_type, default_value)
#define EXPAND_GROUP_MEMBER(member, key_type, default_value) EXPAND_GROUP_MEMBER_IF(member, key_type, GTYPE_IS_POINTER(key_type), default_value)
EXPAND_CONFIG
#undef EXPAND_GROUP
#undef EXPAND_GROUP_MEMBER
#undef EXPAND_GROUP_MEMBER_IF
#undef EXPAND_GROUP_MEMBER_IF_EVAL
#undef EXPAND_GROUP_MEMBER_IF_0
#undef EXPAND_GROUP_MEMBER_IF_1
}
void on_preferences_realize(GtkWidget *w, gpointer builder_ptr) {
GtkWindow *preferences = GTK_WINDOW(w);
GtkBuilder *builder = (GtkBuilder *) builder_ptr;
update_boot_rom_selector(builder);
// Hook up the static preferences
gtk_combo_box_set_active_id(builder_get(GTK_COMBO_BOX, "rewind_duration_selector"), itoa(config.rewind_duration));
gtk_combo_box_set_active_id(builder_get(GTK_COMBO_BOX, "dmg_revision_selector"), config.dmg_revision_name);
gtk_combo_box_set_active_id(builder_get(GTK_COMBO_BOX, "sgb_revision_selector"), config.sgb_revision_name);
gtk_combo_box_set_active_id(builder_get(GTK_COMBO_BOX, "cgb_revision_selector"), config.cgb_revision_name);
gtk_combo_box_set_active_id(builder_get(GTK_COMBO_BOX, "shader_selector"), config.shader);
gtk_combo_box_set_active_id(builder_get(GTK_COMBO_BOX, "color_correction_selector"), config.color_correction_id);
gtk_toggle_button_set_active(builder_get(GTK_TOGGLE_BUTTON, "integer_scaling_toggle"), config.use_integer_scaling);
gtk_toggle_button_set_active(builder_get(GTK_TOGGLE_BUTTON, "aspect_ratio_toggle"), config.keep_aspect_ratio);
gtk_combo_box_set_active_id(builder_get(GTK_COMBO_BOX, "highpass_filter_selector"), config.high_pass_filter_id);
#if ! NDEBUG
gtk_combo_box_set_active_id(builder_get(GTK_COMBO_BOX, "menubar_override_selector"), config.menubar_override);
#else
if (builder_get(GTK_COMBO_BOX, "menubar_override_selector") != NULL) {
gtk_widget_destroy(GTK_WIDGET(builder_get(GTK_COMBO_BOX, "menubar_override_selector")));
gtk_widget_destroy(GTK_WIDGET(builder_get(GTK_COMBO_BOX, "menubar_override_selector_label")));
}
#endif
}
void init_settings(gchar *path, GtkWindow *preferences) {
free_settings();
key_file = g_key_file_new();
if (path != NULL) {
settings_file_path = path;
}
else {
settings_file_path = g_build_filename(g_get_user_config_dir(), SETTINGS_FILE, NULL);
}
load_settings();
}
int load_settings(void) {
GError *error = NULL;
g_print("Trying to load settings from %s\n", settings_file_path);
if (!g_key_file_load_from_file(key_file, settings_file_path, G_KEY_FILE_KEEP_COMMENTS | G_KEY_FILE_KEEP_TRANSLATIONS, &error)) {
if (error->domain == G_FILE_ERROR) {
g_warning("Unable to load %s: %s", settings_file_path, error->message);
}
else if (error->domain == G_KEY_FILE_ERROR) {
g_warning("Failed to parse %s: %s", settings_file_path, error->message);
}
g_error_free(error);
}
load_config_from_key_file(&config, key_file);
print_config(&config);
return 0;
}
void save_settings(void) {
GError *error = NULL;
g_print("Trying to save settings to %s\n", settings_file_path);
save_config_to_key_file(&config, key_file);
if (!g_key_file_save_to_file(key_file, settings_file_path, &error)) {
g_warning ("Failed to save %s: %s", settings_file_path, error->message);
g_error_free(error);
return;
}
}
void free_settings(void) {
if (key_file != NULL) {
g_key_file_free(key_file);
key_file = NULL;
}
}
void update_boot_rom_selector(GtkBuilder *builder) {
GtkComboBoxText *combo_box = builder_get(GTK_COMBO_BOX_TEXT, "boot_rom_selector");
gtk_combo_box_text_remove_all(combo_box);
gtk_combo_box_text_append(combo_box, "auto", "Use Built-in Boot ROMs");
if (config.boot_rom_path != NULL && !g_str_equal(config.boot_rom_path, "auto") && !g_str_equal(config.boot_rom_path, "other")) {
gtk_combo_box_text_append(combo_box, config.boot_rom_path, config.boot_rom_path);
gtk_combo_box_set_active_id(GTK_COMBO_BOX(combo_box), config.boot_rom_path);
}
else {
gtk_combo_box_set_active_id(GTK_COMBO_BOX(combo_box), "auto");
}
gtk_combo_box_text_append_text(combo_box, "<separator>");
gtk_combo_box_text_append(combo_box, "other", "Other");
}
enum menubar_type_t get_show_menubar(void) {
if (config.menubar_override == NULL) goto default_value;
if (g_strcmp0(config.menubar_override, "auto") == 0) {
return MENUBAR_AUTO;
}
else if (g_strcmp0(config.menubar_override, "show_in_shell") == 0) {
return MENUBAR_SHOW_IN_SHELL;
}
else if (g_strcmp0(config.menubar_override, "show_in_window") == 0) {
return MENUBAR_SHOW_IN_WINDOW;
}
else if (g_strcmp0(config.menubar_override, "show_hamburger") == 0) {
return MENUBAR_SHOW_HAMBURGER;
}
// This should not happen
g_warning("Unknown menubar setting: %s\nFalling back to “Auto”\n", config.menubar_override);
default_value: return MENUBAR_AUTO;
}
void set_show_menubar(enum menubar_type_t value) {
switch (value) {
case MENUBAR_AUTO:
config.menubar_override = "auto";
break;
case MENUBAR_SHOW_IN_SHELL:
config.menubar_override = "show_in_shell";
break;
case MENUBAR_SHOW_IN_WINDOW:
config.menubar_override = "show_in_window";
break;
case MENUBAR_SHOW_HAMBURGER:
config.menubar_override = "show_hamburger";
break;
}
}
GB_color_correction_mode_t get_color_correction_mode(void) {
if (config.color_correction_id == NULL) goto default_value;
if (g_strcmp0(config.color_correction_id, "disabled") == 0) {
return GB_COLOR_CORRECTION_DISABLED;
}
else if (g_strcmp0(config.color_correction_id, "correct_color_curves") == 0) {
return GB_COLOR_CORRECTION_CORRECT_CURVES;
}
else if (g_strcmp0(config.color_correction_id, "emulate_hardware") == 0) {
return GB_COLOR_CORRECTION_EMULATE_HARDWARE;
}
else if (g_strcmp0(config.color_correction_id, "preserve_brightness") == 0) {
return GB_COLOR_CORRECTION_PRESERVE_BRIGHTNESS;
}
// This should not happen
g_warning("Unknown color correction mode: %s\nFalling back to “Emulate Hardware”\n", config.color_correction_id);
default_value: return GB_COLOR_CORRECTION_EMULATE_HARDWARE;
}
void set_color_correction_mode(GB_color_correction_mode_t mode) {
switch (mode) {
case GB_COLOR_CORRECTION_DISABLED:
config.color_correction_id = "disabled";
break;
case GB_COLOR_CORRECTION_CORRECT_CURVES:
config.color_correction_id = "correct_color_curves";
break;
case GB_COLOR_CORRECTION_EMULATE_HARDWARE:
config.color_correction_id = "emulate_hardware";
break;
case GB_COLOR_CORRECTION_PRESERVE_BRIGHTNESS:
config.color_correction_id = "preserve_brightness";
break;
}
}
GB_highpass_mode_t get_highpass_mode(void) {
if (config.high_pass_filter_id == NULL) goto default_value;
if (g_strcmp0(config.high_pass_filter_id, "disabled") == 0) {
return GB_HIGHPASS_OFF;
}
else if (g_strcmp0(config.high_pass_filter_id, "emulate_hardware") == 0) {
return GB_HIGHPASS_ACCURATE;
}
else if (g_strcmp0(config.high_pass_filter_id, "preserve_waveform") == 0) {
return GB_HIGHPASS_REMOVE_DC_OFFSET;
}
// This should not happen
g_warning("Unknown highpass mode: %s\nFalling back to “Accurate”\n", config.high_pass_filter_id);
default_value: return GB_HIGHPASS_ACCURATE;
}
void set_highpass_mode(GB_highpass_mode_t mode) {
switch (mode) {
case GB_HIGHPASS_OFF:
config.high_pass_filter_id = "disabled";
break;
case GB_HIGHPASS_MAX:
g_warning("GB_HIGHPASS_MAX is not a valid highpass mode, falling back to “Accurate”.\n");
case GB_HIGHPASS_ACCURATE:
config.high_pass_filter_id = "emulate_hardware";
break;
case GB_HIGHPASS_REMOVE_DC_OFFSET:
config.high_pass_filter_id = "preserve_waveform";
break;
}
}

93
gtk3/settings.h Normal file
View File

@ -0,0 +1,93 @@
#ifndef settings_h
#define settings_h
#include <gtk/gtk.h>
#include <Core/gb.h>
#include "macros.h"
#define SETTINGS_FILE "sameboy-gtk3-settings.ini"
#define FORMAT_FOR_KEY_TYPE(type) FORMAT_FOR_##type
#define FORMAT_FOR_string "%s"
#define FORMAT_FOR_integer "%d"
#define FORMAT_FOR_boolean "%d"
#define GTYPE_OF_KEY_TYPE(type) GTYPE_OF_##type
#define GTYPE_OF_string gchar *
#define GTYPE_OF_integer gint
#define GTYPE_OF_boolean gboolean
#define GTYPE_IS_POINTER(type) GTYPE_IS_POINTER_##type
#define GTYPE_IS_POINTER_string 1
#define GTYPE_IS_POINTER_integer 0
#define GTYPE_IS_POINTER_boolean 0
// Note: Make sure to use a member name only once for the whole config struct
//
// Macro usage:
// EXPAND_CONFIG(EXPAND_GROUP(…) EXPAND_GROUP(…) …)
// EXPAND_GROUP(group_name, group_members)
// EXPAND_GROUP_MEMBER(member_name, key_type, default_value)
#define EXPAND_CONFIG \
EXPAND_GROUP(Emulation, \
EXPAND_GROUP_MEMBER(boot_rom_path, string, "auto") /* overrides search location for boot ROMs by name */ \
EXPAND_GROUP_MEMBER(rewind_duration, integer, 10) \
EXPAND_GROUP_MEMBER(dmg_revision_name, string, "DMG_CPU_C") \
EXPAND_GROUP_MEMBER(sgb_revision_name, string, "SGB2") \
EXPAND_GROUP_MEMBER(cgb_revision_name, string, "CPU_CGB_E") \
) \
EXPAND_GROUP(Video, \
EXPAND_GROUP_MEMBER(shader, string, "NearestNeighbor") \
EXPAND_GROUP_MEMBER(color_correction_id, string, "emulate_hardware") \
EXPAND_GROUP_MEMBER(keep_aspect_ratio, boolean, true) \
EXPAND_GROUP_MEMBER(use_integer_scaling, boolean, true) \
EXPAND_GROUP_MEMBER(menubar_override, string, "auto") \
) \
EXPAND_GROUP(Audio, \
EXPAND_GROUP_MEMBER(high_pass_filter_id, string, "emulate_hardware") \
) \
EXPAND_GROUP(Controls, \
\
)
typedef struct config_t {
#define EXPAND_GROUP(group_name, members) members
#define EXPAND_GROUP_MEMBER(member, key_type, default_value) GTYPE_OF_KEY_TYPE(key_type) member;
EXPAND_CONFIG
#undef EXPAND_GROUP
#undef EXPAND_GROUP_MEMBER
} config_t;
enum menubar_type_t {
MENUBAR_AUTO,
MENUBAR_SHOW_IN_SHELL,
MENUBAR_SHOW_IN_WINDOW,
MENUBAR_SHOW_HAMBURGER
};
gchar* settings_file_path;
GKeyFile *key_file;
config_t config;
void on_preferences_realize(GtkWidget *w, gpointer builder_ptr);
void print_config(config_t *config);
void load_config_from_key_file(config_t *config, GKeyFile *key_file);
void init_settings(gchar *path, GtkWindow *preferences);
int load_settings(void);
void save_settings(void);
void free_settings(void);
void update_boot_rom_selector(GtkBuilder *builder);
enum menubar_type_t get_show_menubar(void);
void set_show_menubar(enum menubar_type_t);
GB_color_correction_mode_t get_color_correction_mode(void);
void set_color_correction_mode(GB_color_correction_mode_t);
GB_highpass_mode_t get_highpass_mode(void);
void set_highpass_mode(GB_highpass_mode_t);
#endif /* settings_h */

205
gtk3/shader.c Normal file
View File

@ -0,0 +1,205 @@
#include "shader.h"
static const char *vertex_shader = "\n\
#version 150 \n\
in vec4 aPosition;\n\
void main(void) {\n\
gl_Position = aPosition;\n\
}\n\
";
static GLuint create_shader(const char *source, GLenum type)
{
// Create the shader object
GLuint shader = glCreateShader(type);
// Load the shader source
glShaderSource(shader, 1, &source, 0);
// Compile the shader
glCompileShader(shader);
// Check for errors
GLint status = 0;
glGetShaderiv(shader, GL_COMPILE_STATUS, &status);
if (status == GL_FALSE) {
GLchar messages[1024];
glGetShaderInfoLog(shader, sizeof(messages), 0, &messages[0]);
g_printerr("GLSL Shader Error: %s", messages);
}
return shader;
}
static GLuint create_program(const char *vsh, const char *fsh)
{
// Build shaders
GLuint vertex_shader = create_shader(vsh, GL_VERTEX_SHADER);
GLuint fragment_shader = create_shader(fsh, GL_FRAGMENT_SHADER);
// Create program
GLuint program = glCreateProgram();
// Attach shaders
glAttachShader(program, vertex_shader);
glAttachShader(program, fragment_shader);
// Link program
glLinkProgram(program);
// Check for errors
GLint status;
glGetProgramiv(program, GL_LINK_STATUS, &status);
if (status == GL_FALSE) {
GLchar messages[1024];
glGetProgramInfoLog(program, sizeof(messages), 0, &messages[0]);
g_printerr("GLSL Program Error: %s", messages);
}
// Delete shaders
glDeleteShader(vertex_shader);
glDeleteShader(fragment_shader);
return program;
}
bool init_shader_with_name(shader_t *shader, const char *name)
{
if (epoxy_gl_version() < 32) {
return false;
}
GError *error = NULL;
GBytes *master_shader_f = NULL;
static const gchar *master_shader_code;
static gsize master_shader_code_size;
static char final_shader_code[0x10801] = {0,};
static signed long filter_token_location = 0;
if (!master_shader_code_size) {
master_shader_f = g_resources_lookup_data(RESOURCE_PREFIX "Shaders/MasterShader.fsh", G_RESOURCE_LOOKUP_FLAGS_NONE, &error);
master_shader_code = g_bytes_get_data(master_shader_f, &master_shader_code_size);
if (!master_shader_f) {
g_printerr("Failed to load master shader: %s", error->message);
g_error_free(error);
return false;
}
filter_token_location = strstr(master_shader_code, "{filter}") - master_shader_code;
if (filter_token_location < 0) {
g_error_free(error);
return false;
}
}
char shader_path[1024];
g_snprintf(shader_path, sizeof(shader_path), RESOURCE_PREFIX "Shaders/%s.fsh", name);
GBytes *shader_f = g_resources_lookup_data(shader_path, G_RESOURCE_LOOKUP_FLAGS_NONE, &error);
if (!shader_f) {
g_printerr("Failed to load shader \"%s\": %s", shader_path, error->message);
g_error_free(error);
return false;
}
gsize shader_code_size;
const gchar *shader_code = g_bytes_get_data(shader_f, &shader_code_size);
memset(final_shader_code, 0, sizeof(final_shader_code));
memcpy(final_shader_code, master_shader_code, filter_token_location);
strcpy(final_shader_code + filter_token_location, shader_code);
strcat(final_shader_code + filter_token_location,
master_shader_code + filter_token_location + sizeof("{filter}") - 1);
g_bytes_unref(shader_f);
shader->program = create_program(vertex_shader, final_shader_code);
// Attributes
shader->position_attribute = glGetAttribLocation(shader->program, "aPosition");
// Uniforms
shader->resolution_uniform = glGetUniformLocation(shader->program, "output_resolution");
shader->origin_uniform = glGetUniformLocation(shader->program, "origin");
glGenTextures(1, &shader->texture);
glBindTexture(GL_TEXTURE_2D, shader->texture);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
glBindTexture(GL_TEXTURE_2D, 0);
shader->texture_uniform = glGetUniformLocation(shader->program, "image");
glGenTextures(1, &shader->previous_texture);
glBindTexture(GL_TEXTURE_2D, shader->previous_texture);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
glBindTexture(GL_TEXTURE_2D, 0);
shader->previous_texture_uniform = glGetUniformLocation(shader->program, "previous_image");
shader->mix_previous_uniform = glGetUniformLocation(shader->program, "mix_previous");
// Program
glUseProgram(shader->program);
GLuint vao;
glGenVertexArrays(1, &vao);
glBindVertexArray(vao);
GLuint vbo;
glGenBuffers(1, &vbo);
// Attributes
static GLfloat const quad[16] = {
-1.f, -1.f, 0, 1,
-1.f, +1.f, 0, 1,
+1.f, -1.f, 0, 1,
+1.f, +1.f, 0, 1,
};
glBindBuffer(GL_ARRAY_BUFFER, vbo);
glBufferData(GL_ARRAY_BUFFER, sizeof(quad), quad, GL_STATIC_DRAW);
glEnableVertexAttribArray(shader->position_attribute);
glVertexAttribPointer(shader->position_attribute, 4, GL_FLOAT, GL_FALSE, 0, 0);
return true;
}
void render_bitmap_with_shader(shader_t *shader, void *bitmap, void *previous,
unsigned source_width, unsigned source_height,
unsigned x, unsigned y, unsigned w, unsigned h)
{
glUseProgram(shader->program);
glUniform2f(shader->origin_uniform, x, y);
glUniform2f(shader->resolution_uniform, w, h);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, shader->texture);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, source_width, source_height, 0, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, bitmap);
glUniform1i(shader->texture_uniform, 0);
glUniform1i(shader->mix_previous_uniform, previous != NULL);
if (previous) {
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, shader->previous_texture);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, source_width, source_height, 0, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, previous);
glUniform1i(shader->previous_texture_uniform, 1);
}
glBindFragDataLocation(shader->program, 0, "frag_color");
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
}
void free_shader(shader_t *shader)
{
GLint major = 0, minor = 0;
glGetIntegerv(GL_MAJOR_VERSION, &major);
glGetIntegerv(GL_MINOR_VERSION, &minor);
if (major * 0x100 + minor < 0x302) {
return;
}
glDeleteProgram(shader->program);
glDeleteTextures(1, &shader->texture);
glDeleteTextures(1, &shader->previous_texture);
}

30
gtk3/shader.h Normal file
View File

@ -0,0 +1,30 @@
#ifndef shader_h
#define shader_h
#include <epoxy/gl.h>
#include <stdbool.h>
#include <string.h>
#include <glib.h>
#include <gio/gio.h>
typedef struct shader_s {
GLuint resolution_uniform;
GLuint origin_uniform;
GLuint texture_uniform;
GLuint previous_texture_uniform;
GLuint mix_previous_uniform;
GLuint position_attribute;
GLuint texture;
GLuint previous_texture;
GLuint program;
bool compat_mode;
} shader_t;
bool init_shader_with_name(shader_t *shader, const char *name);
void render_bitmap_with_shader(shader_t *shader, void *bitmap, void *previous,
unsigned source_width, unsigned source_height,
unsigned x, unsigned y, unsigned w, unsigned h);
void free_shader(struct shader_s *shader);
#endif /* shader_h */