[GTK3] Convert debugger console into GTK widget
This commit is contained in:
parent
bef38d16d2
commit
55a258ad0f
288
gtk3/console_window.c
Normal file
288
gtk3/console_window.c
Normal file
@ -0,0 +1,288 @@
|
|||||||
|
#include "console_window.h"
|
||||||
|
#include "util.h"
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
struct _ConsoleWindow {
|
||||||
|
GtkWindowClass parent_class;
|
||||||
|
|
||||||
|
GtkEntry *input;
|
||||||
|
GtkTextView *output;
|
||||||
|
GtkTextView *sidebar_input;
|
||||||
|
GtkTextView *sidebar_output;
|
||||||
|
|
||||||
|
GAsyncQueue *input_queue;
|
||||||
|
GAsyncQueue *output_queue;
|
||||||
|
|
||||||
|
bool should_clear;
|
||||||
|
bool log_to_sidebar;
|
||||||
|
bool clear_sidebar;
|
||||||
|
|
||||||
|
GList *command_history;
|
||||||
|
};
|
||||||
|
|
||||||
|
G_DEFINE_TYPE(ConsoleWindow, console_window, GTK_TYPE_WINDOW);
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
const char *message;
|
||||||
|
GB_log_attributes attributes;
|
||||||
|
bool sidebar;
|
||||||
|
} AttributedMessage;
|
||||||
|
|
||||||
|
static void console_window_init(ConsoleWindow *self) {
|
||||||
|
gtk_widget_init_template(GTK_WIDGET(self));
|
||||||
|
|
||||||
|
GtkTextBuffer *text_buf = gtk_text_view_get_buffer(self->output);
|
||||||
|
GtkTextTagTable *tag_table = 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, "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_view_set_buffer(
|
||||||
|
self->sidebar_output,
|
||||||
|
gtk_text_buffer_new(tag_table)
|
||||||
|
);
|
||||||
|
|
||||||
|
gtk_text_buffer_set_text(gtk_text_view_get_buffer(self->sidebar_input), "registers\nbacktrace\n", -1);
|
||||||
|
|
||||||
|
self->input_queue = g_async_queue_new();
|
||||||
|
self->output_queue = g_async_queue_new();
|
||||||
|
self->log_to_sidebar = false;
|
||||||
|
self->clear_sidebar = false;
|
||||||
|
self->command_history = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void console_window_realize(GtkWidget *widget) {
|
||||||
|
ConsoleWindow *self = (ConsoleWindow *)widget;
|
||||||
|
|
||||||
|
GTK_WIDGET_CLASS(console_window_parent_class)->realize(widget);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Takes ownership of message
|
||||||
|
static void log_simple(ConsoleWindow *self, const char *message) {
|
||||||
|
AttributedMessage *attr_msg = g_new(AttributedMessage, 1);
|
||||||
|
attr_msg->message = message;
|
||||||
|
attr_msg->attributes = 0;
|
||||||
|
attr_msg->sidebar = false;
|
||||||
|
|
||||||
|
g_async_queue_push(self->output_queue, attr_msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Use command history (arrow key (↑, ↓) events)
|
||||||
|
static void on_input(GtkEntry *input, ConsoleWindow *self) {
|
||||||
|
const gchar *text_ptr = gtk_entry_get_text(input);
|
||||||
|
const gchar *text = NULL;
|
||||||
|
|
||||||
|
if (g_strcmp0("", text_ptr) == 0) {
|
||||||
|
GList *last = g_list_last(self->command_history);
|
||||||
|
if (last) text = last->data;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
text = text_ptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (text) {
|
||||||
|
GList *last = g_list_last(self->command_history);
|
||||||
|
|
||||||
|
// Add command to queue unless it was the last command issued
|
||||||
|
if (!last || g_strcmp0(last->data, text) != 0) {
|
||||||
|
self->command_history = g_list_append(self->command_history, g_strdup(text));
|
||||||
|
}
|
||||||
|
|
||||||
|
g_async_queue_push(self->input_queue, (gpointer) g_strdup(text));
|
||||||
|
gtk_entry_set_text(self->input, "");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void update_sidebar(ConsoleWindow *self, GB_gameboy_t *gb) {
|
||||||
|
if (!GB_debugger_is_stopped(gb)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
GtkTextBuffer *text_buf = gtk_text_view_get_buffer(self->sidebar_input);
|
||||||
|
gint line_count = gtk_text_buffer_get_line_count(text_buf);
|
||||||
|
|
||||||
|
self->log_to_sidebar = true;
|
||||||
|
self->clear_sidebar = true;
|
||||||
|
|
||||||
|
for (unsigned line = 0; line < line_count; ++line) {
|
||||||
|
GtkTextIter start_iter;
|
||||||
|
GtkTextIter end_iter;
|
||||||
|
gunichar ch;
|
||||||
|
|
||||||
|
gtk_text_buffer_get_iter_at_line(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(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);
|
||||||
|
}
|
||||||
|
|
||||||
|
self->log_to_sidebar = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static gboolean console_window_draw(GtkWidget *widget, cairo_t *cr) {
|
||||||
|
ConsoleWindow *self = (ConsoleWindow *)widget;
|
||||||
|
GtkTextBuffer *main_text_buf = gtk_text_view_get_buffer(self->output);
|
||||||
|
GtkTextBuffer *sidebar_text_buf = gtk_text_view_get_buffer(self->sidebar_output);
|
||||||
|
|
||||||
|
if (self->should_clear) {
|
||||||
|
gtk_text_buffer_set_text(main_text_buf, "", -1);
|
||||||
|
gtk_text_buffer_set_text(sidebar_text_buf, "", -1);
|
||||||
|
|
||||||
|
// clear pending log messages
|
||||||
|
while (g_async_queue_try_pop(self->output_queue));
|
||||||
|
|
||||||
|
self->should_clear = false;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
GtkTextIter iter;
|
||||||
|
GtkTextIter start;
|
||||||
|
AttributedMessage *attr_msg = NULL;
|
||||||
|
|
||||||
|
GtkTextIter main_scroll_iter;
|
||||||
|
bool scroll_main = false;
|
||||||
|
|
||||||
|
GtkTextIter sidebar_scroll_iter;
|
||||||
|
bool scroll_sidebar = false;
|
||||||
|
|
||||||
|
if (self->clear_sidebar) {
|
||||||
|
gtk_text_buffer_set_text(sidebar_text_buf, "", -1);
|
||||||
|
self->clear_sidebar = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
while ((attr_msg = g_async_queue_try_pop(self->output_queue))) {
|
||||||
|
GtkTextBuffer *text_buf = attr_msg->sidebar? sidebar_text_buf : main_text_buf;
|
||||||
|
|
||||||
|
gtk_text_buffer_get_end_iter(text_buf, &iter);
|
||||||
|
GtkTextMark *start_mark = gtk_text_buffer_create_mark(text_buf, NULL, &iter, true);
|
||||||
|
|
||||||
|
// give ownership of message to the text buffer
|
||||||
|
gtk_text_buffer_insert(text_buf, &iter, attr_msg->message, -1);
|
||||||
|
gtk_text_buffer_get_iter_at_mark(text_buf, &start, start_mark);
|
||||||
|
|
||||||
|
if (attr_msg->attributes & GB_LOG_BOLD) {
|
||||||
|
gtk_text_buffer_apply_tag_by_name(text_buf, "bold", &start, &iter);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (attr_msg->attributes & GB_LOG_DASHED_UNDERLINE) {
|
||||||
|
gtk_text_buffer_apply_tag_by_name(text_buf, "dashed_underline", &start, &iter);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (attr_msg->attributes & GB_LOG_UNDERLINE) {
|
||||||
|
gtk_text_buffer_apply_tag_by_name(text_buf, "underline", &start, &iter);
|
||||||
|
}
|
||||||
|
|
||||||
|
gtk_text_buffer_delete_mark(text_buf, start_mark);
|
||||||
|
|
||||||
|
if (attr_msg->sidebar) {
|
||||||
|
scroll_sidebar = true;
|
||||||
|
sidebar_scroll_iter = iter;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
scroll_main = true;
|
||||||
|
main_scroll_iter = iter;
|
||||||
|
}
|
||||||
|
|
||||||
|
g_free(attr_msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (scroll_main) {
|
||||||
|
scroll_to_bottom(self->output, gtk_text_buffer_create_mark(main_text_buf, NULL, &main_scroll_iter, true));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (scroll_sidebar) {
|
||||||
|
scroll_to_bottom(self->sidebar_output, gtk_text_buffer_create_mark(sidebar_text_buf, NULL, &sidebar_scroll_iter, true));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return GTK_WIDGET_CLASS(console_window_parent_class)->draw(widget, cr);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void console_window_class_init(ConsoleWindowClass *class) {
|
||||||
|
gtk_widget_class_set_template_from_resource(GTK_WIDGET_CLASS(class), RESOURCE_PREFIX "ui/console_window.ui");
|
||||||
|
|
||||||
|
gtk_widget_class_bind_template_child(GTK_WIDGET_CLASS(class), ConsoleWindow, input);
|
||||||
|
gtk_widget_class_bind_template_child(GTK_WIDGET_CLASS(class), ConsoleWindow, output);
|
||||||
|
gtk_widget_class_bind_template_child(GTK_WIDGET_CLASS(class), ConsoleWindow, sidebar_input);
|
||||||
|
gtk_widget_class_bind_template_child(GTK_WIDGET_CLASS(class), ConsoleWindow, sidebar_output);
|
||||||
|
|
||||||
|
gtk_widget_class_bind_template_callback(GTK_WIDGET_CLASS(class), on_input);
|
||||||
|
|
||||||
|
GTK_WIDGET_CLASS(class)->realize = console_window_realize;
|
||||||
|
GTK_WIDGET_CLASS(class)->draw = console_window_draw;
|
||||||
|
}
|
||||||
|
|
||||||
|
ConsoleWindow *console_window_new(void) {
|
||||||
|
return g_object_new(CONSOLE_WINDOW_TYPE, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
// This function gets called every VBlank while the emulation is running.
|
||||||
|
char *console_get_async_input(ConsoleWindow *self, GB_gameboy_t *gb) {
|
||||||
|
self->clear_sidebar = true;
|
||||||
|
|
||||||
|
char *command = (char *)g_async_queue_try_pop(self->input_queue);
|
||||||
|
|
||||||
|
if (command) {
|
||||||
|
gchar *msg = g_strdup_printf("> %s\n", command);
|
||||||
|
log_simple(self, msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
return command;
|
||||||
|
}
|
||||||
|
|
||||||
|
// This will only be called if the debugger is in stopped mode (after a breakpoint hit for example),
|
||||||
|
// thus we block the emulation thread until input is available.
|
||||||
|
char *console_get_sync_input(ConsoleWindow *self, GB_gameboy_t *gb) {
|
||||||
|
update_sidebar(self, gb);
|
||||||
|
|
||||||
|
char *command = (char *)g_async_queue_pop(self->input_queue);
|
||||||
|
|
||||||
|
if (command) {
|
||||||
|
gchar *msg = g_strdup_printf("> %s\n", command);
|
||||||
|
log_simple(self, msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
return command;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Queues a message to be logged to the console
|
||||||
|
void console_log(ConsoleWindow *self, const char *message, GB_log_attributes attributes) {
|
||||||
|
if (!message || g_str_equal("", message)) return;
|
||||||
|
|
||||||
|
AttributedMessage *attr_msg = g_new(AttributedMessage, 1);
|
||||||
|
attr_msg->message = g_strdup(message);
|
||||||
|
attr_msg->attributes = attributes;
|
||||||
|
attr_msg->sidebar = self->log_to_sidebar;
|
||||||
|
|
||||||
|
g_async_queue_push(self->output_queue, attr_msg);
|
||||||
|
|
||||||
|
// mark as dirty
|
||||||
|
gtk_widget_queue_draw(GTK_WIDGET(self));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Marks the console as to be cleared on the next redraw
|
||||||
|
void console_clear(ConsoleWindow *self) {
|
||||||
|
self->should_clear = true;
|
||||||
|
|
||||||
|
// mark as dirty
|
||||||
|
gtk_widget_queue_draw(GTK_WIDGET(self));
|
||||||
|
}
|
15
gtk3/console_window.h
Normal file
15
gtk3/console_window.h
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
#ifndef console_window_h
|
||||||
|
#define console_window_h
|
||||||
|
|
||||||
|
#include <gtk/gtk.h>
|
||||||
|
#include <Core/gb.h>
|
||||||
|
|
||||||
|
#define CONSOLE_WINDOW_TYPE (console_window_get_type())
|
||||||
|
G_DECLARE_FINAL_TYPE(ConsoleWindow, console_window, SAMEBOY, CONSOLE_WINDOW, GtkWindow)
|
||||||
|
|
||||||
|
ConsoleWindow *console_window_new(void);
|
||||||
|
char *console_get_async_input(ConsoleWindow *self, GB_gameboy_t *gb);
|
||||||
|
char *console_get_sync_input(ConsoleWindow *self, GB_gameboy_t *gb);
|
||||||
|
void console_log(ConsoleWindow *self, const char *message, GB_log_attributes attributes);
|
||||||
|
void console_clear(ConsoleWindow *self);
|
||||||
|
#endif
|
340
gtk3/main.c
340
gtk3/main.c
@ -14,6 +14,7 @@
|
|||||||
#include "check_menu_radio_group.h"
|
#include "check_menu_radio_group.h"
|
||||||
|
|
||||||
#include "gb_screen.h"
|
#include "gb_screen.h"
|
||||||
|
#include "console_window.h"
|
||||||
#include "preferences_window.h"
|
#include "preferences_window.h"
|
||||||
#include "vram_viewer_window.h"
|
#include "vram_viewer_window.h"
|
||||||
|
|
||||||
@ -52,10 +53,6 @@ static GuiData gui_data = {
|
|||||||
.stopping = false,
|
.stopping = false,
|
||||||
.stopped = false,
|
.stopped = false,
|
||||||
|
|
||||||
.in_sync_input = false,
|
|
||||||
.log_to_sidebar = false,
|
|
||||||
.should_clear_sidebar = false,
|
|
||||||
|
|
||||||
.audio_initialized = false,
|
.audio_initialized = false,
|
||||||
.border_mode_changed = false,
|
.border_mode_changed = false,
|
||||||
|
|
||||||
@ -355,199 +352,6 @@ static void rumble_callback(GB_gameboy_t *gb, double amp) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static gboolean clear_sidebar(void) {
|
|
||||||
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);
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
static gboolean scroll_to_bottom(GtkTextView *textview, GtkTextMark *mark) {
|
|
||||||
GtkTextBuffer *buffer = gtk_text_view_get_buffer(textview);
|
|
||||||
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 gboolean append_pending_output(void) {
|
|
||||||
g_rec_mutex_lock(&gui_data.console_output_lock);
|
|
||||||
|
|
||||||
if (gui_data.should_clear_sidebar) {
|
|
||||||
clear_sidebar();
|
|
||||||
gui_data.should_clear_sidebar = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (gtk_text_buffer_get_char_count(gui_data.pending_console_output) > 0) {
|
|
||||||
GtkTextView *text_view = builder_get(GTK_TEXT_VIEW, gui_data.log_to_sidebar? "console_sidebar_output" : "console_screen");
|
|
||||||
GtkTextBuffer *text_buf = gtk_text_view_get_buffer(text_view);
|
|
||||||
GtkTextIter start;
|
|
||||||
GtkTextIter end;
|
|
||||||
gtk_text_buffer_get_start_iter(gui_data.pending_console_output, &start);
|
|
||||||
gtk_text_buffer_get_end_iter(gui_data.pending_console_output, &end);
|
|
||||||
|
|
||||||
GtkTextIter iter;
|
|
||||||
gtk_text_buffer_get_end_iter(text_buf, &iter);
|
|
||||||
gtk_text_buffer_insert_range(text_buf, &iter, &start, &end);
|
|
||||||
|
|
||||||
scroll_to_bottom(text_view, gtk_text_buffer_create_mark(text_buf, NULL, &iter, true));
|
|
||||||
|
|
||||||
gtk_text_buffer_set_text(gui_data.pending_console_output, "", -1);
|
|
||||||
}
|
|
||||||
|
|
||||||
g_rec_mutex_unlock(&gui_data.console_output_lock);
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
static gboolean update_debugger_sidebar(GB_gameboy_t *gb) {
|
|
||||||
if (!GB_debugger_is_stopped(gb)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (gui_data.main_thread != g_thread_self()) {
|
|
||||||
g_idle_add((GSourceFunc) update_debugger_sidebar, gb);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
g_rec_mutex_lock(&gui_data.console_output_lock);
|
|
||||||
gui_data.should_clear_sidebar = true;
|
|
||||||
append_pending_output();
|
|
||||||
gui_data.log_to_sidebar = true;
|
|
||||||
g_rec_mutex_unlock(&gui_data.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(&gui_data.console_output_lock);
|
|
||||||
append_pending_output();
|
|
||||||
gui_data.log_to_sidebar = false;
|
|
||||||
g_rec_mutex_unlock(&gui_data.console_output_lock);
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void console_log(GB_gameboy_t *gb, const char *string, GB_log_attributes attributes) {
|
|
||||||
g_rec_mutex_lock(&gui_data.console_output_lock);
|
|
||||||
|
|
||||||
if (string != NULL && !g_str_equal("", string)) {
|
|
||||||
GtkTextIter iter;
|
|
||||||
GtkTextIter start;
|
|
||||||
|
|
||||||
// Append attributed text to "gui_data.pending_console_output" GtkTextBuffer
|
|
||||||
gtk_text_buffer_get_end_iter(gui_data.pending_console_output, &iter);
|
|
||||||
GtkTextMark *start_mark = gtk_text_buffer_create_mark(gui_data.pending_console_output, NULL, &iter, true);
|
|
||||||
gtk_text_buffer_insert(gui_data.pending_console_output, &iter, g_strdup(string), -1);
|
|
||||||
gtk_text_buffer_get_iter_at_mark(gui_data.pending_console_output, &start, start_mark);
|
|
||||||
|
|
||||||
if (attributes & GB_LOG_BOLD) {
|
|
||||||
gtk_text_buffer_apply_tag_by_name(gui_data.pending_console_output, "bold", &start, &iter);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (attributes & GB_LOG_DASHED_UNDERLINE) {
|
|
||||||
gtk_text_buffer_apply_tag_by_name(gui_data.pending_console_output, "dashed_underline", &start, &iter);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (attributes & GB_LOG_UNDERLINE) {
|
|
||||||
gtk_text_buffer_apply_tag_by_name(gui_data.pending_console_output, "underline", &start, &iter);
|
|
||||||
}
|
|
||||||
|
|
||||||
gtk_text_buffer_delete_mark(gui_data.pending_console_output, start_mark);
|
|
||||||
|
|
||||||
g_idle_add((GSourceFunc) append_pending_output, NULL);
|
|
||||||
}
|
|
||||||
|
|
||||||
g_rec_mutex_unlock(&gui_data.console_output_lock);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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) {
|
|
||||||
update_debugger_sidebar(gb);
|
|
||||||
console_log(gb, "> ", 0);
|
|
||||||
gui_data.in_sync_input = true;
|
|
||||||
|
|
||||||
g_mutex_lock(&gui_data.debugger_input_mutex);
|
|
||||||
g_cond_wait(&gui_data.debugger_input_cond, &gui_data.debugger_input_mutex);
|
|
||||||
|
|
||||||
gchar *input = NULL;
|
|
||||||
const gchar *_input = g_ptr_array_index(gui_data.debugger_input_queue, 0);
|
|
||||||
input = g_strdup(_input);
|
|
||||||
gpointer ptr = g_ptr_array_remove_index(gui_data.debugger_input_queue, 0);
|
|
||||||
if (ptr) g_free(ptr);
|
|
||||||
|
|
||||||
g_mutex_unlock(&gui_data.debugger_input_mutex);
|
|
||||||
|
|
||||||
gui_data.in_sync_input = false;
|
|
||||||
|
|
||||||
return input;
|
|
||||||
}
|
|
||||||
|
|
||||||
static char *async_console_input(GB_gameboy_t *gb) {
|
|
||||||
// TODO: This is rather ugly
|
|
||||||
g_idle_add((GSourceFunc) clear_sidebar, NULL);
|
|
||||||
|
|
||||||
if (gui_data.debugger_input_queue->len == 0) return NULL;
|
|
||||||
|
|
||||||
g_mutex_lock(&gui_data.debugger_input_mutex);
|
|
||||||
|
|
||||||
gchar *input = NULL;
|
|
||||||
const gchar *_input = g_ptr_array_index(gui_data.debugger_input_queue, 0);
|
|
||||||
if (_input) {
|
|
||||||
input = g_strdup(_input);
|
|
||||||
gpointer ptr = g_ptr_array_remove_index(gui_data.debugger_input_queue, 0);
|
|
||||||
if (ptr) g_free(ptr);
|
|
||||||
}
|
|
||||||
|
|
||||||
g_mutex_unlock(&gui_data.debugger_input_mutex);
|
|
||||||
|
|
||||||
return input;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Creating these items in the UI defintion files was buggy in some desktop
|
// Creating these items in the UI defintion files was buggy in some desktop
|
||||||
// environments and the manual call of `g_signal_connect` was needed anyway
|
// environments and the manual call of `g_signal_connect` was needed anyway
|
||||||
// because the UI definition can’t define string arguments for signal handlers.
|
// because the UI definition can’t define string arguments for signal handlers.
|
||||||
@ -867,31 +671,47 @@ static void load_boot_rom(GB_gameboy_t *gb, GB_boot_rom_t type) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static char *wrapped_console_get_async_input(GB_gameboy_t *gb) {
|
||||||
|
return console_get_async_input(gui_data.console, gb);
|
||||||
|
}
|
||||||
|
|
||||||
|
static char *wrapped_console_get_sync_input(GB_gameboy_t *gb) {
|
||||||
|
return console_get_sync_input(gui_data.console, gb);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void wrapped_console_log(GB_gameboy_t *gb, const char *message, GB_log_attributes attributes) {
|
||||||
|
console_log(gui_data.console, message, attributes);
|
||||||
|
}
|
||||||
|
|
||||||
static void init(void) {
|
static void init(void) {
|
||||||
if (GB_is_inited(&gb)) return;
|
if (GB_is_inited(&gb)) return;
|
||||||
|
|
||||||
GB_init(&gb, config_get_model_type(&gui_data));
|
GB_init(&gb, config_get_model_type(&gui_data));
|
||||||
|
|
||||||
GB_set_vblank_callback(&gb, vblank);
|
GB_set_vblank_callback(&gb, vblank);
|
||||||
GB_set_pixels_output(&gb, gb_screen_get_current_buffer(gui_data.screen));
|
|
||||||
GB_set_rgb_encode_callback(&gb, rgb_encode);
|
GB_set_rgb_encode_callback(&gb, rgb_encode);
|
||||||
GB_set_sample_rate(&gb, GB_audio_get_sample_rate());
|
|
||||||
|
GB_set_pixels_output(&gb, gb_screen_get_current_buffer(gui_data.screen));
|
||||||
GB_set_color_correction_mode(&gb, config_get_color_correction_mode());
|
GB_set_color_correction_mode(&gb, config_get_color_correction_mode());
|
||||||
GB_set_highpass_filter_mode(&gb, config_get_highpass_mode());
|
|
||||||
GB_set_rewind_length(&gb, config.emulation.rewind_duration);
|
|
||||||
GB_set_update_input_hint_callback(&gb, handle_events);
|
|
||||||
GB_apu_set_sample_callback(&gb, gb_audio_callback);
|
|
||||||
GB_set_input_callback(&gb, sync_console_input);
|
|
||||||
GB_set_async_input_callback(&gb, async_console_input);
|
|
||||||
GB_set_log_callback(&gb, console_log);
|
|
||||||
GB_set_boot_rom_load_callback(&gb, load_boot_rom);
|
|
||||||
|
|
||||||
GB_set_rumble_callback(&gb, rumble_callback);
|
|
||||||
GB_set_rumble_mode(&gb, config_get_rumble_mode());
|
|
||||||
|
|
||||||
if (config_get_display_border_mode() <= GB_BORDER_ALWAYS) {
|
if (config_get_display_border_mode() <= GB_BORDER_ALWAYS) {
|
||||||
GB_set_border_mode(&gb, config_get_display_border_mode());
|
GB_set_border_mode(&gb, config_get_display_border_mode());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
GB_apu_set_sample_callback(&gb, gb_audio_callback);
|
||||||
|
|
||||||
|
GB_set_sample_rate(&gb, GB_audio_get_sample_rate());
|
||||||
|
GB_set_highpass_filter_mode(&gb, config_get_highpass_mode());
|
||||||
|
|
||||||
|
GB_set_log_callback(&gb, wrapped_console_log);
|
||||||
|
GB_set_input_callback(&gb, wrapped_console_get_sync_input);
|
||||||
|
GB_set_async_input_callback(&gb, wrapped_console_get_async_input);
|
||||||
|
|
||||||
|
GB_set_boot_rom_load_callback(&gb, load_boot_rom);
|
||||||
|
GB_set_update_input_hint_callback(&gb, handle_events);
|
||||||
|
GB_set_rumble_callback(&gb, rumble_callback);
|
||||||
|
|
||||||
|
GB_set_rumble_mode(&gb, config_get_rumble_mode());
|
||||||
|
GB_set_rewind_length(&gb, config.emulation.rewind_duration);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void reset(void) {
|
static void reset(void) {
|
||||||
@ -1170,11 +990,11 @@ static void startup(GApplication *app, gpointer null_ptr) {
|
|||||||
init_config(app, gui_data.cli_options.config_path, &gui_data.config_modification_date);
|
init_config(app, gui_data.cli_options.config_path, &gui_data.config_modification_date);
|
||||||
|
|
||||||
gui_data.screen = gb_screen_new(gui_data.cli_options.force_software_renderer);
|
gui_data.screen = gb_screen_new(gui_data.cli_options.force_software_renderer);
|
||||||
|
gui_data.console = console_window_new();
|
||||||
gui_data.preferences = preferences_window_new(&gb);
|
gui_data.preferences = preferences_window_new(&gb);
|
||||||
gui_data.vram_viewer = vram_viewer_window_new();
|
gui_data.vram_viewer = vram_viewer_window_new();
|
||||||
gui_data.memory_viewer = GTK_WINDOW(get_object("memory_viewer"));
|
gui_data.memory_viewer = GTK_WINDOW(get_object("memory_viewer"));
|
||||||
|
|
||||||
gui_data.console = GTK_WINDOW(get_object("console"));
|
|
||||||
gui_data.printer = GTK_WINDOW(get_object("printer"));
|
gui_data.printer = GTK_WINDOW(get_object("printer"));
|
||||||
|
|
||||||
if (config.audio.sample_rate == -1) {
|
if (config.audio.sample_rate == -1) {
|
||||||
@ -1322,40 +1142,12 @@ static void connect_signal_handlers(GApplication *app) {
|
|||||||
g_signal_connect(gui_data.preferences, "pref-update::audio-sample-rate", G_CALLBACK(on_preferences_notify_sample_rate), NULL);
|
g_signal_connect(gui_data.preferences, "pref-update::audio-sample-rate", G_CALLBACK(on_preferences_notify_sample_rate), NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void setup_console(void) {
|
|
||||||
GtkTextView *text_view = builder_get(GTK_TEXT_VIEW, "console_screen");
|
|
||||||
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, "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);
|
|
||||||
|
|
||||||
g_mutex_init(&gui_data.debugger_input_mutex);
|
|
||||||
g_cond_init(&gui_data.debugger_input_cond);
|
|
||||||
g_rec_mutex_init(&gui_data.console_output_lock);
|
|
||||||
|
|
||||||
if (!gui_data.debugger_input_queue) {
|
|
||||||
gui_data.debugger_input_queue = g_ptr_array_sized_new(4);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!gui_data.pending_console_output) {
|
|
||||||
gui_data.pending_console_output = gtk_text_buffer_new(gtk_text_buffer_get_tag_table(text_buf));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// This function gets called when the GApplication gets activated, i.e. it is ready to show widgets.
|
// This function gets called when the GApplication gets activated, i.e. it is ready to show widgets.
|
||||||
static void activate(GApplication *app, gpointer null_ptr) {
|
static void activate(GApplication *app, gpointer null_ptr) {
|
||||||
init_audio();
|
init_audio();
|
||||||
init_controllers();
|
init_controllers();
|
||||||
|
|
||||||
connect_signal_handlers(app);
|
connect_signal_handlers(app);
|
||||||
// create_canvas();
|
|
||||||
setup_console();
|
|
||||||
|
|
||||||
if (gui_data.cli_options.fullscreen) {
|
if (gui_data.cli_options.fullscreen) {
|
||||||
gtk_window_fullscreen(GTK_WINDOW(gui_data.main_window));
|
gtk_window_fullscreen(GTK_WINDOW(gui_data.main_window));
|
||||||
@ -1401,16 +1193,17 @@ static void activate_about(GSimpleAction *action, GVariant *parameter, gpointer
|
|||||||
gtk_widget_hide(GTK_WIDGET(dialog));
|
gtk_widget_hide(GTK_WIDGET(dialog));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// app.preferences GAction
|
||||||
|
// Opens the preferences window
|
||||||
|
static void activate_preferences(GSimpleAction *action, GVariant *parameter, gpointer app) {
|
||||||
|
gtk_widget_show_all(GTK_WIDGET(gui_data.preferences));
|
||||||
|
}
|
||||||
|
|
||||||
// app.show_console GAction
|
// app.show_console GAction
|
||||||
// Opens the console
|
// Opens the console
|
||||||
static void activate_show_console(GSimpleAction *action, GVariant *parameter, gpointer app) {
|
static void activate_show_console(GSimpleAction *action, GVariant *parameter, gpointer app) {
|
||||||
if (gui_data.debugger_input_queue) {
|
gtk_widget_show_all(GTK_WIDGET(gui_data.console));
|
||||||
while (gui_data.debugger_input_queue->len) {
|
|
||||||
g_ptr_array_remove_index_fast(gui_data.debugger_input_queue, gui_data.debugger_input_queue->len - 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
gtk_widget_show_all(builder_get(GTK_WIDGET, "console"));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// app.open_gtk_debugger GAction
|
// app.open_gtk_debugger GAction
|
||||||
@ -1431,6 +1224,12 @@ static void activate_open_vram_viewer(GSimpleAction *action, GVariant *parameter
|
|||||||
gtk_widget_show_all(GTK_WIDGET(gui_data.vram_viewer));
|
gtk_widget_show_all(GTK_WIDGET(gui_data.vram_viewer));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// app.clear_console GAction
|
||||||
|
// Clears the debugger console
|
||||||
|
static void activate_clear_console(GSimpleAction *action, GVariant *parameter, gpointer app) {
|
||||||
|
console_clear(gui_data.console);
|
||||||
|
}
|
||||||
|
|
||||||
// Closes a ROM
|
// Closes a ROM
|
||||||
static void close_rom(void) {
|
static void close_rom(void) {
|
||||||
stop();
|
stop();
|
||||||
@ -1478,30 +1277,12 @@ static void activate_close(GSimpleAction *action, GVariant *parameter, gpointer
|
|||||||
close_rom();
|
close_rom();
|
||||||
}
|
}
|
||||||
|
|
||||||
// app.preferences GAction
|
|
||||||
// Opens the preferences window
|
|
||||||
static void activate_preferences(GSimpleAction *action, GVariant *parameter, gpointer app) {
|
|
||||||
gtk_widget_show_all(GTK_WIDGET(gui_data.preferences));
|
|
||||||
}
|
|
||||||
|
|
||||||
// app.quit GAction
|
// app.quit GAction
|
||||||
// Exits the application
|
// Exits the application
|
||||||
static void activate_quit(GSimpleAction *action, GVariant *parameter, gpointer app) {
|
static void activate_quit(GSimpleAction *action, GVariant *parameter, gpointer app) {
|
||||||
quit();
|
quit();
|
||||||
}
|
}
|
||||||
|
|
||||||
// app.clear_console GAction
|
|
||||||
// Clears the debugger console
|
|
||||||
static void activate_clear_console(GSimpleAction *action, GVariant *parameter, gpointer app) {
|
|
||||||
g_rec_mutex_lock(&gui_data.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(&gui_data.console_output_lock);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void on_mute_changed(GSimpleAction *action, GVariant *value, gpointer user_data_ptr) {
|
static void on_mute_changed(GSimpleAction *action, GVariant *value, gpointer user_data_ptr) {
|
||||||
config.audio.muted = g_variant_get_boolean(value);
|
config.audio.muted = g_variant_get_boolean(value);
|
||||||
|
|
||||||
@ -1543,35 +1324,6 @@ G_MODULE_EXPORT void on_open_recent_activate(GtkRecentChooser *chooser, gpointer
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
G_MODULE_EXPORT void console_on_enter(GtkWidget *w, gpointer user_data_ptr) {
|
|
||||||
GtkEntry *input = GTK_ENTRY(w);
|
|
||||||
const gchar *_text = gtk_entry_get_text(input);
|
|
||||||
gchar *text = g_strdup(_text);
|
|
||||||
|
|
||||||
if (g_strcmp0("", text) == 0 && g_strcmp0("", gui_data.last_console_input) < 0) {
|
|
||||||
text = g_strdup(gui_data.last_console_input);
|
|
||||||
}
|
|
||||||
else if (text) {
|
|
||||||
if (gui_data.last_console_input != NULL) g_free(gui_data.last_console_input);
|
|
||||||
gui_data.last_console_input = g_strdup(text);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!gui_data.in_sync_input) {
|
|
||||||
console_log(&gb, "> ", 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
console_log(&gb, text, 0);
|
|
||||||
console_log(&gb, "\n", 0);
|
|
||||||
|
|
||||||
g_mutex_lock(&gui_data.debugger_input_mutex);
|
|
||||||
g_ptr_array_add(gui_data.debugger_input_queue, (gpointer)text);
|
|
||||||
g_cond_signal(&gui_data.debugger_input_cond);
|
|
||||||
g_mutex_unlock(&gui_data.debugger_input_mutex);
|
|
||||||
|
|
||||||
// clear input
|
|
||||||
gtk_entry_set_text(input, "");
|
|
||||||
}
|
|
||||||
|
|
||||||
int main(int argc, char *argv[]) {
|
int main(int argc, char *argv[]) {
|
||||||
gui_data.main_thread = g_thread_self();
|
gui_data.main_thread = g_thread_self();
|
||||||
|
|
||||||
|
202
gtk3/resources/ui/console_window.ui
Normal file
202
gtk3/resources/ui/console_window.ui
Normal file
@ -0,0 +1,202 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!-- Generated with glade 3.36.0
|
||||||
|
|
||||||
|
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-2019 Lior Halphon -->
|
||||||
|
<!-- interface-authors Maximilian Mader -->
|
||||||
|
<template class="ConsoleWindow">
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<property name="title" translatable="yes">Debug Console</property>
|
||||||
|
<property name="default_width">920</property>
|
||||||
|
<property name="default_height">400</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkBox">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<property name="orientation">vertical</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkBox">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkScrolledWindow">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="shadow_type">in</property>
|
||||||
|
<property name="min_content_width">600</property>
|
||||||
|
<property name="min_content_height">376</property>
|
||||||
|
<property name="max_content_width">600</property>
|
||||||
|
<property name="max_content_height">376</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkTextView" id="output">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="editable">False</property>
|
||||||
|
<property name="wrap_mode">word</property>
|
||||||
|
<property name="left_margin">5</property>
|
||||||
|
<property name="right_margin">5</property>
|
||||||
|
<property name="top_margin">5</property>
|
||||||
|
<property name="bottom_margin">5</property>
|
||||||
|
<property name="cursor_visible">False</property>
|
||||||
|
<property name="accepts_tab">False</property>
|
||||||
|
<property name="monospace">True</property>
|
||||||
|
<style>
|
||||||
|
<class name="border-none"/>
|
||||||
|
</style>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
<style>
|
||||||
|
<class name="border-none"/>
|
||||||
|
<class name="border-right"/>
|
||||||
|
</style>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">True</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="position">0</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkBox">
|
||||||
|
<property name="width_request">320</property>
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<property name="orientation">vertical</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkScrolledWindow">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="shadow_type">in</property>
|
||||||
|
<property name="min_content_width">320</property>
|
||||||
|
<property name="min_content_height">80</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkTextView" id="sidebar_input">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="wrap_mode">word</property>
|
||||||
|
<property name="left_margin">5</property>
|
||||||
|
<property name="right_margin">5</property>
|
||||||
|
<property name="top_margin">5</property>
|
||||||
|
<property name="bottom_margin">5</property>
|
||||||
|
<property name="accepts_tab">False</property>
|
||||||
|
<property name="monospace">True</property>
|
||||||
|
<style>
|
||||||
|
<class name="border-none"/>
|
||||||
|
</style>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
<style>
|
||||||
|
<class name="border-none"/>
|
||||||
|
<class name="border-bottom"/>
|
||||||
|
</style>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="position">0</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkScrolledWindow">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="shadow_type">in</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkTextView" id="sidebar_output">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="editable">False</property>
|
||||||
|
<property name="wrap_mode">word</property>
|
||||||
|
<property name="left_margin">5</property>
|
||||||
|
<property name="right_margin">5</property>
|
||||||
|
<property name="top_margin">5</property>
|
||||||
|
<property name="bottom_margin">5</property>
|
||||||
|
<property name="accepts_tab">False</property>
|
||||||
|
<property name="monospace">True</property>
|
||||||
|
<style>
|
||||||
|
<class name="border-none"/>
|
||||||
|
</style>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
<style>
|
||||||
|
<class name="border-none"/>
|
||||||
|
</style>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">True</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="position">1</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="position">1</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<style>
|
||||||
|
<class name="border-none"/>
|
||||||
|
<class name="border-bottom"/>
|
||||||
|
</style>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">True</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="position">0</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkEntry" id="input">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="margin_top">3</property>
|
||||||
|
<property name="margin_bottom">3</property>
|
||||||
|
<property name="placeholder_text" translatable="yes">Console input</property>
|
||||||
|
<signal name="activate" handler="on_input" swapped="no"/>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="position">1</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
<child type="titlebar">
|
||||||
|
<placeholder/>
|
||||||
|
</child>
|
||||||
|
<style>
|
||||||
|
<class name="debug-console"/>
|
||||||
|
</style>
|
||||||
|
</template>
|
||||||
|
</interface>
|
@ -72,173 +72,6 @@ Maximilian Mader https://github.com/max-m</property>
|
|||||||
</child>
|
</child>
|
||||||
</object>
|
</object>
|
||||||
|
|
||||||
<object class="GtkWindow" id="console">
|
|
||||||
<property name="can_focus">False</property>
|
|
||||||
<property name="title" translatable="yes">Debug Console</property>
|
|
||||||
<property name="default_width">920</property>
|
|
||||||
<property name="default_height">400</property>
|
|
||||||
<child>
|
|
||||||
<object class="GtkBox">
|
|
||||||
<property name="visible">True</property>
|
|
||||||
<property name="can_focus">False</property>
|
|
||||||
<property name="orientation">vertical</property>
|
|
||||||
<child>
|
|
||||||
<object class="GtkBox">
|
|
||||||
<property name="visible">True</property>
|
|
||||||
<property name="can_focus">False</property>
|
|
||||||
<child>
|
|
||||||
<object class="GtkScrolledWindow">
|
|
||||||
<property name="visible">True</property>
|
|
||||||
<property name="can_focus">True</property>
|
|
||||||
<property name="shadow_type">in</property>
|
|
||||||
<property name="min_content_width">600</property>
|
|
||||||
<property name="min_content_height">376</property>
|
|
||||||
<property name="max_content_width">600</property>
|
|
||||||
<property name="max_content_height">376</property>
|
|
||||||
<child>
|
|
||||||
<object class="GtkTextView" id="console_screen">
|
|
||||||
<property name="visible">True</property>
|
|
||||||
<property name="can_focus">True</property>
|
|
||||||
<property name="editable">False</property>
|
|
||||||
<property name="wrap_mode">word</property>
|
|
||||||
<property name="left_margin">5</property>
|
|
||||||
<property name="right_margin">5</property>
|
|
||||||
<property name="top_margin">5</property>
|
|
||||||
<property name="bottom_margin">5</property>
|
|
||||||
<property name="cursor_visible">False</property>
|
|
||||||
<property name="accepts_tab">False</property>
|
|
||||||
<property name="monospace">True</property>
|
|
||||||
<style>
|
|
||||||
<class name="border-none"/>
|
|
||||||
</style>
|
|
||||||
</object>
|
|
||||||
</child>
|
|
||||||
<style>
|
|
||||||
<class name="border-none"/>
|
|
||||||
<class name="border-right"/>
|
|
||||||
</style>
|
|
||||||
</object>
|
|
||||||
<packing>
|
|
||||||
<property name="expand">True</property>
|
|
||||||
<property name="fill">True</property>
|
|
||||||
<property name="position">0</property>
|
|
||||||
</packing>
|
|
||||||
</child>
|
|
||||||
<child>
|
|
||||||
<object class="GtkBox">
|
|
||||||
<property name="width_request">320</property>
|
|
||||||
<property name="visible">True</property>
|
|
||||||
<property name="can_focus">False</property>
|
|
||||||
<property name="orientation">vertical</property>
|
|
||||||
<child>
|
|
||||||
<object class="GtkScrolledWindow">
|
|
||||||
<property name="visible">True</property>
|
|
||||||
<property name="can_focus">True</property>
|
|
||||||
<property name="shadow_type">in</property>
|
|
||||||
<property name="min_content_width">320</property>
|
|
||||||
<property name="min_content_height">80</property>
|
|
||||||
<child>
|
|
||||||
<object class="GtkTextView" id="console_sidebar_input">
|
|
||||||
<property name="visible">True</property>
|
|
||||||
<property name="can_focus">True</property>
|
|
||||||
<property name="wrap_mode">word</property>
|
|
||||||
<property name="left_margin">5</property>
|
|
||||||
<property name="right_margin">5</property>
|
|
||||||
<property name="top_margin">5</property>
|
|
||||||
<property name="bottom_margin">5</property>
|
|
||||||
<property name="accepts_tab">False</property>
|
|
||||||
<property name="monospace">True</property>
|
|
||||||
<style>
|
|
||||||
<class name="border-none"/>
|
|
||||||
</style>
|
|
||||||
</object>
|
|
||||||
</child>
|
|
||||||
<style>
|
|
||||||
<class name="border-none"/>
|
|
||||||
<class name="border-bottom"/>
|
|
||||||
</style>
|
|
||||||
</object>
|
|
||||||
<packing>
|
|
||||||
<property name="expand">False</property>
|
|
||||||
<property name="fill">True</property>
|
|
||||||
<property name="position">0</property>
|
|
||||||
</packing>
|
|
||||||
</child>
|
|
||||||
<child>
|
|
||||||
<object class="GtkScrolledWindow">
|
|
||||||
<property name="visible">True</property>
|
|
||||||
<property name="can_focus">True</property>
|
|
||||||
<property name="shadow_type">in</property>
|
|
||||||
<child>
|
|
||||||
<object class="GtkTextView" id="console_sidebar_output">
|
|
||||||
<property name="visible">True</property>
|
|
||||||
<property name="can_focus">True</property>
|
|
||||||
<property name="editable">False</property>
|
|
||||||
<property name="wrap_mode">word</property>
|
|
||||||
<property name="left_margin">5</property>
|
|
||||||
<property name="right_margin">5</property>
|
|
||||||
<property name="top_margin">5</property>
|
|
||||||
<property name="bottom_margin">5</property>
|
|
||||||
<property name="accepts_tab">False</property>
|
|
||||||
<property name="monospace">True</property>
|
|
||||||
<style>
|
|
||||||
<class name="border-none"/>
|
|
||||||
</style>
|
|
||||||
</object>
|
|
||||||
</child>
|
|
||||||
<style>
|
|
||||||
<class name="border-none"/>
|
|
||||||
</style>
|
|
||||||
</object>
|
|
||||||
<packing>
|
|
||||||
<property name="expand">True</property>
|
|
||||||
<property name="fill">True</property>
|
|
||||||
<property name="position">1</property>
|
|
||||||
</packing>
|
|
||||||
</child>
|
|
||||||
</object>
|
|
||||||
<packing>
|
|
||||||
<property name="expand">False</property>
|
|
||||||
<property name="fill">True</property>
|
|
||||||
<property name="position">1</property>
|
|
||||||
</packing>
|
|
||||||
</child>
|
|
||||||
<style>
|
|
||||||
<class name="border-none"/>
|
|
||||||
<class name="border-bottom"/>
|
|
||||||
</style>
|
|
||||||
</object>
|
|
||||||
<packing>
|
|
||||||
<property name="expand">True</property>
|
|
||||||
<property name="fill">True</property>
|
|
||||||
<property name="position">0</property>
|
|
||||||
</packing>
|
|
||||||
</child>
|
|
||||||
<child>
|
|
||||||
<object class="GtkEntry" id="console_input">
|
|
||||||
<property name="visible">True</property>
|
|
||||||
<property name="can_focus">True</property>
|
|
||||||
<property name="margin_top">3</property>
|
|
||||||
<property name="margin_bottom">3</property>
|
|
||||||
<property name="placeholder_text" translatable="yes">Console input</property>
|
|
||||||
<signal name="activate" handler="console_on_enter" swapped="no"/>
|
|
||||||
</object>
|
|
||||||
<packing>
|
|
||||||
<property name="expand">False</property>
|
|
||||||
<property name="fill">True</property>
|
|
||||||
<property name="position">1</property>
|
|
||||||
</packing>
|
|
||||||
</child>
|
|
||||||
</object>
|
|
||||||
</child>
|
|
||||||
<child type="titlebar">
|
|
||||||
<placeholder/>
|
|
||||||
</child>
|
|
||||||
<style>
|
|
||||||
<class name="debug-console"/>
|
|
||||||
</style>
|
|
||||||
</object>
|
|
||||||
|
|
||||||
<object class="GtkWindow" id="memory_viewer">
|
<object class="GtkWindow" id="memory_viewer">
|
||||||
<property name="can_focus">False</property>
|
<property name="can_focus">False</property>
|
||||||
<property name="title" translatable="yes">Memory Viewer</property>
|
<property name="title" translatable="yes">Memory Viewer</property>
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
<gresources>
|
<gresources>
|
||||||
<gresource prefix="/io/github/sameboy">
|
<gresource prefix="/io/github/sameboy">
|
||||||
<file preprocess="xml-stripblanks" compressed="true">ui/window.ui</file>
|
<file preprocess="xml-stripblanks" compressed="true">ui/window.ui</file>
|
||||||
|
<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/preferences_window.ui</file>
|
||||||
<file preprocess="xml-stripblanks" compressed="true">ui/vram_viewer_window.ui</file>
|
<file preprocess="xml-stripblanks" compressed="true">ui/vram_viewer_window.ui</file>
|
||||||
<!--
|
<!--
|
||||||
|
14
gtk3/types.h
14
gtk3/types.h
@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
#include "SDL.h"
|
#include "SDL.h"
|
||||||
#include "gb_screen.h"
|
#include "gb_screen.h"
|
||||||
|
#include "console_window.h"
|
||||||
#include "preferences_window.h"
|
#include "preferences_window.h"
|
||||||
#include "vram_viewer_window.h"
|
#include "vram_viewer_window.h"
|
||||||
|
|
||||||
@ -41,24 +42,13 @@ typedef struct GuiData {
|
|||||||
GtkBox *main_window_container;
|
GtkBox *main_window_container;
|
||||||
|
|
||||||
GbScreen *screen;
|
GbScreen *screen;
|
||||||
|
ConsoleWindow *console;
|
||||||
PreferencesWindow *preferences;
|
PreferencesWindow *preferences;
|
||||||
VramViewerWindow *vram_viewer;
|
VramViewerWindow *vram_viewer;
|
||||||
|
|
||||||
GtkWindow *memory_viewer;
|
GtkWindow *memory_viewer;
|
||||||
GtkWindow *console;
|
|
||||||
GtkWindow *printer;
|
GtkWindow *printer;
|
||||||
|
|
||||||
// Debugger state
|
|
||||||
GtkTextBuffer *pending_console_output;
|
|
||||||
gboolean in_sync_input;
|
|
||||||
gchar *last_console_input;
|
|
||||||
gboolean log_to_sidebar;
|
|
||||||
gboolean should_clear_sidebar;
|
|
||||||
GMutex debugger_input_mutex;
|
|
||||||
GCond debugger_input_cond;
|
|
||||||
GRecMutex console_output_lock;
|
|
||||||
GPtrArray *debugger_input_queue;
|
|
||||||
|
|
||||||
// Audio and video
|
// Audio and video
|
||||||
bool audio_initialized;
|
bool audio_initialized;
|
||||||
bool border_mode_changed;
|
bool border_mode_changed;
|
||||||
|
15
gtk3/util.c
15
gtk3/util.c
@ -142,3 +142,18 @@ void set_combo_box_row_separator_func(GtkContainer *container) {
|
|||||||
|
|
||||||
g_list_free(children);
|
g_list_free(children);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
gboolean scroll_to_bottom(GtkTextView *textview, GtkTextMark *mark) {
|
||||||
|
GtkTextBuffer *buffer = gtk_text_view_get_buffer(textview);
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
@ -25,4 +25,6 @@ GtkWidget *menubar_to_menu(GtkMenuBar *menubar);
|
|||||||
gboolean is_separator(GtkTreeModel *model, GtkTreeIter *iter, gpointer data);
|
gboolean is_separator(GtkTreeModel *model, GtkTreeIter *iter, gpointer data);
|
||||||
void set_combo_box_row_separator_func(GtkContainer *container);
|
void set_combo_box_row_separator_func(GtkContainer *container);
|
||||||
|
|
||||||
|
gboolean scroll_to_bottom(GtkTextView *textview, GtkTextMark *mark);
|
||||||
|
|
||||||
#endif
|
#endif
|
Loading…
Reference in New Issue
Block a user