[GTK3] Implement debugger console sidebar

This commit is contained in:
Maximilian Mader 2020-04-10 04:10:28 +02:00
parent a551c09964
commit 66e2e75102
Signed by: Max
GPG Key ID: F71D56A3151C4FB3
4 changed files with 231 additions and 133 deletions

1
.gitignore vendored
View File

@ -3,6 +3,7 @@ build
# intermediate source files generated at build time # intermediate source files generated at build time
gtk3/sameboy-gtk3-resources.c gtk3/sameboy-gtk3-resources.c
gtk3/resources/gtk3/ gtk3/resources/gtk3/
gtk3/resources/ui/#*#
# temporary backup file # temporary backup file
*.*~ *.*~

View File

@ -23,6 +23,8 @@ static SDL_GameController *controller = NULL;
static SDL_AudioSpec want_aspec, have_aspec; static SDL_AudioSpec want_aspec, have_aspec;
static SDL_AudioDeviceID device_id; static SDL_AudioDeviceID device_id;
static const GThread *main_thread;
static GtkApplication *main_application; static GtkApplication *main_application;
static GtkBuilder *builder; static GtkBuilder *builder;
static GtkGLArea *gl_area; static GtkGLArea *gl_area;
@ -72,12 +74,15 @@ static uint8_t oamHeight;
static uint8_t pressed_buttons; static uint8_t pressed_buttons;
static GtkTextBuffer *pending_console_output = NULL;
static gboolean in_sync_input = false; static gboolean in_sync_input = false;
static gchar *last_console_input = NULL; static gchar *last_console_input = NULL;
static gboolean log_to_sidebar = false;
static gboolean should_clear_sidebar = false;
static GMutex debugger_input_mutex; static GMutex debugger_input_mutex;
static GCond debugger_input_cond; static GCond debugger_input_cond;
static GMutex console_output_lock; static GRecMutex console_output_lock;
static GPtrArray *debugger_input_queue; static GPtrArray *debugger_input_queue;
// List of GActions for the `app` prefix // List of GActions for the `app` prefix
@ -97,9 +102,12 @@ static const GActionEntry app_entries[] = {
{ "change_model", NULL, "s", "@s 'CGB'", on_model_changed }, { "change_model", NULL, "s", "@s 'CGB'", on_model_changed },
{ "toggle_mute", NULL, NULL, "false", on_mute_changed }, { "toggle_mute", NULL, NULL, "false", on_mute_changed },
{ "pause", NULL, NULL, "false", on_pause_changed }, { "pause", NULL, NULL, "false", on_pause_changed },
{ "clear_console", activate_clear_console, NULL, NULL, NULL },
}; };
int main(int argc, char *argv[]) { int main(int argc, char *argv[]) {
main_thread = g_thread_self();
// initialize GB_model_t to invalid value // initialize GB_model_t to invalid value
gui_data.cli_options.model = -1; gui_data.cli_options.model = -1;
gui_data.prev_model = -1; gui_data.prev_model = -1;
@ -386,7 +394,13 @@ static void gb_audio_callback(GB_gameboy_t *gb, GB_sample_t *sample) {
SDL_QueueAudio(device_id, sample, sizeof(*sample)); SDL_QueueAudio(device_id, sample, sizeof(*sample));
} }
// Console TODO:
// TODO: clear sidebar when switching to async mode
// TODO: Command history (up / down arrow in input)
// TODO: reverse search of commands
// TODO: search in output
static char *sync_console_input(GB_gameboy_t *gb) { static char *sync_console_input(GB_gameboy_t *gb) {
update_debugger_sidebar(gb);
console_log(gb, "> ", 0); console_log(gb, "> ", 0);
in_sync_input = true; in_sync_input = true;
@ -403,11 +417,6 @@ static char *sync_console_input(GB_gameboy_t *gb) {
in_sync_input = false; in_sync_input = false;
// clear sidebar
GtkTextView *sidebar_output = builder_get(GTK_TEXT_VIEW, "console_sidebar_output");
GtkTextBuffer *sidebar_text_buf = gtk_text_view_get_buffer(sidebar_output);
gtk_text_buffer_set_text(sidebar_text_buf, "", -1);
return input; return input;
} }
@ -429,50 +438,140 @@ static char *async_console_input(GB_gameboy_t *gb) {
return input; return input;
} }
static void on_console_log(gpointer user_data_gptr) { static void update_debugger_sidebar(GB_gameboy_t *gb) {
LogData *log_data = (LogData *)user_data_gptr; if (!GB_debugger_is_stopped(gb)) {
GB_gameboy_t *gb = log_data->gb; return;
GB_log_attributes attributes = log_data->attributes; }
g_mutex_lock(&console_output_lock); if (main_thread != g_thread_self()) {
g_idle_add((GSourceFunc) update_debugger_sidebar, gb);
return;
}
GtkTextView *text_view = builder_get(GTK_TEXT_VIEW, "console_screen"); g_rec_mutex_lock(&console_output_lock);
GtkTextBuffer *text_buf = gtk_text_view_get_buffer(text_view); should_clear_sidebar = true;
append_pending_output();
log_to_sidebar = true;
g_rec_mutex_unlock(&console_output_lock);
GtkTextView *sidebar_input = builder_get(GTK_TEXT_VIEW, "console_sidebar_input");
GtkTextBuffer *sidebar_input_text_buf = gtk_text_view_get_buffer(sidebar_input);
gint line_count = gtk_text_buffer_get_line_count(sidebar_input_text_buf);
for (unsigned line = 0; line < line_count; ++line) {
GtkTextIter start_iter;
GtkTextIter end_iter;
gunichar ch;
gtk_text_buffer_get_iter_at_line(sidebar_input_text_buf, &start_iter, line);
end_iter = start_iter;
do {
ch = gtk_text_iter_get_char(&end_iter);
if (!gtk_text_iter_forward_char(&end_iter)) {
break;
}
}
while (ch != '\n');
gchar *cmd = gtk_text_buffer_get_text(sidebar_input_text_buf, &start_iter, &end_iter, false);
g_strchug(cmd); // trim leading whitespace
g_strchomp(cmd); // trim trailing whitespace
if (g_strcmp0("", cmd) != 0) {
char *duped = g_strdup(cmd);
GB_attributed_log(gb, GB_LOG_BOLD, "%s:\n", duped);
GB_debugger_execute_command(gb, duped);
GB_log(gb, "\n");
g_free(duped);
}
g_free(cmd);
}
g_rec_mutex_lock(&console_output_lock);
append_pending_output();
log_to_sidebar = false;
g_rec_mutex_unlock(&console_output_lock);
}
static gboolean scroll_to_bottom(GtkTextView *textview, GtkTextMark *mark) {
GtkTextBuffer *buffer = gtk_text_view_get_buffer(textview);
GtkTextIter iter; GtkTextIter iter;
gtk_text_buffer_get_end_iter(buffer, &iter);
gtk_text_iter_set_line_offset(&iter, 0);
gtk_text_buffer_move_mark(buffer, mark, &iter);
gtk_text_view_scroll_to_mark(textview, mark, 0.0, TRUE, 0.0, 0.10);
gtk_text_buffer_delete_mark(buffer, mark);
return TRUE;
}
static void append_pending_output(void) {
g_rec_mutex_lock(&console_output_lock);
if (should_clear_sidebar) {
GtkTextView *sidebar_output = builder_get(GTK_TEXT_VIEW, "console_sidebar_output");
GtkTextBuffer *sidebar_output_text_buf = gtk_text_view_get_buffer(sidebar_output);
gtk_text_buffer_set_text(sidebar_output_text_buf, "", -1);
should_clear_sidebar = FALSE;
}
if (gtk_text_buffer_get_char_count(pending_console_output) > 0) {
GtkTextView *text_view = builder_get(GTK_TEXT_VIEW, log_to_sidebar? "console_sidebar_output" : "console_screen");
GtkTextBuffer *text_buf = gtk_text_view_get_buffer(text_view);
GtkTextIter start; GtkTextIter start;
GtkTextIter end;
gtk_text_buffer_get_start_iter(pending_console_output, &start);
gtk_text_buffer_get_end_iter(pending_console_output, &end);
GtkTextIter iter;
gtk_text_buffer_get_end_iter(text_buf, &iter); gtk_text_buffer_get_end_iter(text_buf, &iter);
GtkTextMark *start_mark = gtk_text_buffer_create_mark(text_buf, NULL, &iter, TRUE); gtk_text_buffer_insert_range(text_buf, &iter, &start, &end);
gtk_text_buffer_insert(text_buf, &iter, g_strdup(log_data->string), -1);
gtk_text_buffer_get_iter_at_mark(text_buf, &start, start_mark);
if (attributes & GB_LOG_BOLD) { scroll_to_bottom(text_view, gtk_text_buffer_create_mark(text_buf, NULL, &iter, TRUE));
gtk_text_buffer_apply_tag_by_name(text_buf, "bold", &start, &iter);
gtk_text_buffer_set_text(pending_console_output, "", -1);
} }
if (attributes & GB_LOG_DASHED_UNDERLINE) { g_rec_mutex_unlock(&console_output_lock);
gtk_text_buffer_apply_tag_by_name(text_buf, "dashed_underline", &start, &iter);
}
if (attributes & GB_LOG_UNDERLINE) {
gtk_text_buffer_apply_tag_by_name(text_buf, "underline", &start, &iter);
}
g_free((gpointer)log_data->string);
g_free(log_data);
gtk_text_buffer_delete_mark(text_buf, start_mark);
g_mutex_unlock(&console_output_lock);
} }
static void console_log(GB_gameboy_t *gb, const char *string, GB_log_attributes attributes) { static void console_log(GB_gameboy_t *gb, const char *string, GB_log_attributes attributes) {
if (string != NULL && !g_str_equal("", string)) { g_rec_mutex_lock(&console_output_lock);
LogData *log_data = g_malloc(sizeof(LogData));
log_data->gb = gb;
log_data->string = g_strdup(string);
log_data->attributes = attributes;
g_idle_add((GSourceFunc) on_console_log, log_data); if (string != NULL && !g_str_equal("", string)) {
GtkTextIter iter;
GtkTextIter start;
// Append attributed text to "pending_console_output" GtkTextBuffer
gtk_text_buffer_get_end_iter(pending_console_output, &iter);
GtkTextMark *start_mark = gtk_text_buffer_create_mark(pending_console_output, NULL, &iter, TRUE);
gtk_text_buffer_insert(pending_console_output, &iter, g_strdup(string), -1);
gtk_text_buffer_get_iter_at_mark(pending_console_output, &start, start_mark);
if (attributes & GB_LOG_BOLD) {
gtk_text_buffer_apply_tag_by_name(pending_console_output, "bold", &start, &iter);
} }
if (attributes & GB_LOG_DASHED_UNDERLINE) {
gtk_text_buffer_apply_tag_by_name(pending_console_output, "dashed_underline", &start, &iter);
}
if (attributes & GB_LOG_UNDERLINE) {
gtk_text_buffer_apply_tag_by_name(pending_console_output, "underline", &start, &iter);
}
gtk_text_buffer_delete_mark(pending_console_output, start_mark);
g_idle_add((GSourceFunc) append_pending_output, NULL);
}
g_rec_mutex_unlock(&console_output_lock);
} }
// Returns a `GApplication`s `GMenuModel` by ID // Returns a `GApplication`s `GMenuModel` by ID
@ -798,6 +897,12 @@ static void activate(GApplication *app, gpointer gui_data_gptr) {
GtkTextView *text_view = builder_get(GTK_TEXT_VIEW, "console_screen"); GtkTextView *text_view = builder_get(GTK_TEXT_VIEW, "console_screen");
GtkTextBuffer *text_buf = gtk_text_view_get_buffer(text_view); GtkTextBuffer *text_buf = gtk_text_view_get_buffer(text_view);
gtk_text_view_set_buffer(
builder_get(GTK_TEXT_VIEW, "console_sidebar_output"),
gtk_text_buffer_new(gtk_text_buffer_get_tag_table(text_buf))
);
gtk_text_buffer_create_tag(text_buf, "bold", "weight", PANGO_WEIGHT_BOLD, NULL); gtk_text_buffer_create_tag(text_buf, "bold", "weight", PANGO_WEIGHT_BOLD, NULL);
gtk_text_buffer_create_tag(text_buf, "underline", "underline", PANGO_UNDERLINE_SINGLE, "underline-set", TRUE, NULL); gtk_text_buffer_create_tag(text_buf, "underline", "underline", PANGO_UNDERLINE_SINGLE, "underline-set", TRUE, NULL);
gtk_text_buffer_create_tag(text_buf, "dashed_underline", "underline", PANGO_UNDERLINE_DOUBLE, "underline-set", TRUE, NULL); gtk_text_buffer_create_tag(text_buf, "dashed_underline", "underline", PANGO_UNDERLINE_DOUBLE, "underline-set", TRUE, NULL);
@ -811,12 +916,16 @@ static void activate(GApplication *app, gpointer gui_data_gptr) {
g_mutex_init(&debugger_input_mutex); g_mutex_init(&debugger_input_mutex);
g_cond_init(&debugger_input_cond); g_cond_init(&debugger_input_cond);
g_mutex_init(&console_output_lock); g_rec_mutex_init(&console_output_lock);
if (!debugger_input_queue) { if (!debugger_input_queue) {
debugger_input_queue = g_ptr_array_sized_new(4); debugger_input_queue = g_ptr_array_sized_new(4);
} }
if (!pending_console_output) {
pending_console_output = gtk_text_buffer_new(gtk_text_buffer_get_tag_table(text_buf));
}
// Start the emulation thread // Start the emulation thread
run(gui_data); run(gui_data);
} }
@ -1015,6 +1124,18 @@ static void activate_reset(GSimpleAction *action, GVariant *parameter, gpointer
} }
} }
// app.clear_console GAction
// Clears the debugger console
static void activate_clear_console(GSimpleAction *action, GVariant *parameter, gpointer app) {
g_rec_mutex_lock(&console_output_lock);
GtkTextView *text_view = builder_get(GTK_TEXT_VIEW, "console_screen");
GtkTextBuffer *text_buf = gtk_text_view_get_buffer(text_view);
gtk_text_buffer_set_text(text_buf, "", -1);
g_rec_mutex_unlock(&console_output_lock);
}
static void on_model_changed(GSimpleAction *action, GVariant *value, gpointer user_data) { static void on_model_changed(GSimpleAction *action, GVariant *value, gpointer user_data) {
if (!GB_is_inited(&gb)) { if (!GB_is_inited(&gb)) {
g_simple_action_set_state(action, value); g_simple_action_set_state(action, value);

View File

@ -70,6 +70,8 @@ static gboolean init_audio();
static void gb_audio_callback(GB_gameboy_t *gb, GB_sample_t *sample); static void gb_audio_callback(GB_gameboy_t *gb, GB_sample_t *sample);
static char *sync_console_input(GB_gameboy_t *gb); static char *sync_console_input(GB_gameboy_t *gb);
static char *async_console_input(GB_gameboy_t *gb); static char *async_console_input(GB_gameboy_t *gb);
static void update_debugger_sidebar(GB_gameboy_t *gb);
static void append_pending_output();
static void on_console_log(gpointer user_data_gptr); static void on_console_log(gpointer user_data_gptr);
static void console_log(GB_gameboy_t *gb, const char *string, GB_log_attributes attributes); static void console_log(GB_gameboy_t *gb, const char *string, GB_log_attributes attributes);
@ -107,6 +109,7 @@ static void activate_open(GSimpleAction *action, GVariant *parameter, gpointer a
static void activate_preferences(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); static void activate_quit(GSimpleAction *action, GVariant *parameter, gpointer app);
static void activate_reset(GSimpleAction *action, GVariant *parameter, gpointer app); static void activate_reset(GSimpleAction *action, GVariant *parameter, gpointer app);
static void activate_clear_console(GSimpleAction *action, GVariant *parameter, gpointer app);
static void on_model_changed(GSimpleAction *action, GVariant *value, gpointer user_data); static void on_model_changed(GSimpleAction *action, GVariant *value, gpointer user_data);
static void on_mute_changed(GSimpleAction *action, GVariant *value, gpointer user_data); static void on_mute_changed(GSimpleAction *action, GVariant *value, gpointer user_data);

View File

@ -139,10 +139,6 @@ Maximilian Mader https://github.com/max-m</property>
<property name="min_content_height">376</property> <property name="min_content_height">376</property>
<property name="max_content_width">600</property> <property name="max_content_width">600</property>
<property name="max_content_height">376</property> <property name="max_content_height">376</property>
<child>
<object class="GtkViewport">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child> <child>
<object class="GtkTextView" id="console_screen"> <object class="GtkTextView" id="console_screen">
<property name="visible">True</property> <property name="visible">True</property>
@ -161,11 +157,6 @@ Maximilian Mader https://github.com/max-m</property>
</style> </style>
</object> </object>
</child> </child>
<style>
<class name="border-none"/>
</style>
</object>
</child>
<style> <style>
<class name="border-none"/> <class name="border-none"/>
<class name="border-right"/> <class name="border-right"/>
@ -190,10 +181,6 @@ Maximilian Mader https://github.com/max-m</property>
<property name="shadow_type">in</property> <property name="shadow_type">in</property>
<property name="min_content_width">320</property> <property name="min_content_width">320</property>
<property name="min_content_height">80</property> <property name="min_content_height">80</property>
<child>
<object class="GtkViewport">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child> <child>
<object class="GtkTextView" id="console_sidebar_input"> <object class="GtkTextView" id="console_sidebar_input">
<property name="visible">True</property> <property name="visible">True</property>
@ -210,11 +197,6 @@ Maximilian Mader https://github.com/max-m</property>
</style> </style>
</object> </object>
</child> </child>
<style>
<class name="border-none"/>
</style>
</object>
</child>
<style> <style>
<class name="border-none"/> <class name="border-none"/>
<class name="border-bottom"/> <class name="border-bottom"/>
@ -231,10 +213,6 @@ Maximilian Mader https://github.com/max-m</property>
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">True</property> <property name="can_focus">True</property>
<property name="shadow_type">in</property> <property name="shadow_type">in</property>
<child>
<object class="GtkViewport">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child> <child>
<object class="GtkTextView" id="console_sidebar_output"> <object class="GtkTextView" id="console_sidebar_output">
<property name="visible">True</property> <property name="visible">True</property>
@ -256,11 +234,6 @@ Maximilian Mader https://github.com/max-m</property>
<class name="border-none"/> <class name="border-none"/>
</style> </style>
</object> </object>
</child>
<style>
<class name="border-none"/>
</style>
</object>
<packing> <packing>
<property name="expand">True</property> <property name="expand">True</property>
<property name="fill">True</property> <property name="fill">True</property>
@ -656,23 +629,6 @@ Maximilian Mader https://github.com/max-m</property>
<property name="position">3</property> <property name="position">3</property>
</packing> </packing>
</child> </child>
<child>
<object class="GtkCheckButton" id="integer_scaling_toggle">
<property name="label" translatable="yes">Use Integer Scaling</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="margin_top">5</property>
<property name="margin_bottom">10</property>
<property name="draw_indicator">True</property>
<signal name="toggled" handler="on_use_integer_scaling_changed" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">4</property>
</packing>
</child>
<child> <child>
<object class="GtkCheckButton" id="aspect_ratio_toggle"> <object class="GtkCheckButton" id="aspect_ratio_toggle">
<property name="label" translatable="yes">Keep Aspect Ratio</property> <property name="label" translatable="yes">Keep Aspect Ratio</property>
@ -690,6 +646,23 @@ Maximilian Mader https://github.com/max-m</property>
<property name="position">4</property> <property name="position">4</property>
</packing> </packing>
</child> </child>
<child>
<object class="GtkCheckButton" id="integer_scaling_toggle">
<property name="label" translatable="yes">Use Integer Scaling</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="margin_top">5</property>
<property name="margin_bottom">10</property>
<property name="draw_indicator">True</property>
<signal name="toggled" handler="on_use_integer_scaling_changed" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">4</property>
</packing>
</child>
<child> <child>
<object class="GtkLabel" id="menubar_override_selector_label"> <object class="GtkLabel" id="menubar_override_selector_label">
<property name="can_focus">False</property> <property name="can_focus">False</property>