ChimaraGlkPrivate *glk_data = g_private_get(glk_data_key);
if(glk_data->interrupt_handler)
(*(glk_data->interrupt_handler))();
- shutdown_glk();
+ shutdown_glk_pre();
+ shutdown_glk_post();
+ /* If program is terminated by g_thread_exit() instead of returning from the
+ glk_main() function, then the line in glk_enter() where the "stopped"
+ signal is emitted will not be reached. So we have to emit it here. */
+ if(!glk_data->in_startup)
+ g_signal_emit_by_name(glk_data->self, "stopped");
g_thread_exit(NULL);
}
g_mutex_unlock(glk_data->abort_lock);
}
-/* Internal function: do any cleanup for shutting down the Glk library. */
+/* Internal function: shut down all requests and anything not necessary while
+ showing the last displayed configuration of windows. */
void
-shutdown_glk(void)
+shutdown_glk_pre(void)
{
ChimaraGlkPrivate *glk_data = g_private_get(glk_data_key);
-
+
/* Stop any timers */
glk_request_timer_events(0);
-
+
/* Cancel any pending input requests and flush all window buffers */
winid_t win;
for(win = glk_window_iterate(NULL, NULL); win; win = glk_window_iterate(win, NULL))
break;
case INPUT_REQUEST_NONE:
default:
- ; /* Handle mouse and hyperlink requests */
+ ; /* TODO: Handle mouse and hyperlink requests */
}
-
+
flush_window_buffer(win);
}
-
+
/* Close any open resource files */
if(glk_data->resource_map != NULL) {
giblorb_destroy_map(glk_data->resource_map);
+ glk_data->resource_map = NULL;
glk_stream_close(glk_data->resource_file, NULL);
}
-
- /* Unref the input queues */
- g_async_queue_unref(glk_data->char_input_queue);
- g_async_queue_unref(glk_data->line_input_queue);
-
+
+ /* Empty the input queues */
+ while(g_async_queue_try_pop(glk_data->char_input_queue))
+ ;
+ while(g_async_queue_try_pop(glk_data->line_input_queue))
+ ;
+
/* Wait for any pending window rearrange */
g_mutex_lock(glk_data->arrange_lock);
if(glk_data->needs_rearrange)
g_cond_wait(glk_data->rearranged, glk_data->arrange_lock);
g_mutex_unlock(glk_data->arrange_lock);
+}
- /* Default handler for 'stopped' unloads the plugin, so be absolutely sure
- we're not calling any dispatch callbacks after this point */
- if(!glk_data->in_startup)
- g_signal_emit_by_name(glk_data->self, "stopped");
+/* Internal function: do any Glk-thread cleanup for shutting down the Glk library. */
+void
+shutdown_glk_post(void)
+{
+ ChimaraGlkPrivate *glk_data = g_private_get(glk_data_key);
+
+ /* Free all opaque objects; can't iterate normally, because the objects are
+ being removed from the global iteration lists */
+ if(glk_data->root_window)
+ glk_window_close(glk_data->root_window->data, NULL);
+ g_assert(glk_data->root_window == NULL);
+ strid_t str;
+ while( (str = glk_stream_iterate(NULL, NULL)) )
+ glk_stream_close(str, NULL);
+ frefid_t fref;
+ while( (fref = glk_fileref_iterate(NULL, NULL)) )
+ glk_fileref_destroy(fref);
+ schanid_t sch;
+ while( (sch = glk_schannel_iterate(NULL, NULL)) )
+ glk_schannel_destroy(sch);
+
+ /* Empty the event queue */
+ g_mutex_lock(glk_data->event_lock);
+ g_queue_foreach(glk_data->event_queue, (GFunc)g_free, NULL);
+ g_queue_clear(glk_data->event_queue);
+ g_mutex_unlock(glk_data->event_lock);
+
+ /* Reset the abort signaling mechanism */
+ g_mutex_lock(glk_data->abort_lock);
+ glk_data->abort_signalled = FALSE;
+ g_mutex_unlock(glk_data->abort_lock);
+
+ /* Reset arrangement mechanism */
+ g_mutex_lock(glk_data->arrange_lock);
+ glk_data->needs_rearrange = FALSE;
+ glk_data->ignore_next_arrange_event = FALSE;
+ g_mutex_unlock(glk_data->arrange_lock);
+
+ /* Unref input queues (they are not destroyed because the main thread stil holds a ref */
+ g_async_queue_unref(glk_data->char_input_queue);
+ g_async_queue_unref(glk_data->line_input_queue);
+
+ /* Reset other stuff */
+ glk_data->interrupt_handler = NULL;
+ g_free(glk_data->current_dir);
+ glk_data->current_dir = NULL;
+ /* Remove the dispatch callbacks */
+ glk_data->register_obj = NULL;
+ glk_data->unregister_obj = NULL;
+ glk_data->register_arr = NULL;
+ glk_data->unregister_arr = NULL;
- _chimara_glk_free_nonwindow_private_data(glk_data);
- glk_data->needs_reset = TRUE;
+ /* Leave the style_initialized flag as it is, since the CSS file is a widget property */
}
#include <glib.h>
G_GNUC_INTERNAL void check_for_abort(void);
-G_GNUC_INTERNAL void shutdown_glk(void);
+G_GNUC_INTERNAL void shutdown_glk_pre(void);
+G_GNUC_INTERNAL void shutdown_glk_post(void);
#endif
/* Hashtable containing the default and current style */
struct StyleSet *default_styles;
struct StyleSet *current_styles;
+ gboolean style_initialized; /* Have styles been initialized */
+ /* Final message displayed when game exits */
+ gchar *final_message;
/* *** Threading data *** */
/* Whether program is running */
/* Abort mechanism */
GMutex *abort_lock;
gboolean abort_signalled;
+ /* Key press after shutdown mechanism */
+ GMutex *shutdown_lock;
+ GCond *shutdown_key_pressed;
/* Window arrangement locks */
GMutex *arrange_lock;
GCond *rearranged;
void (*unregister_obj)(void *, glui32, gidispatch_rock_t);
gidispatch_rock_t (*register_arr)(void *, glui32, char *);
void (*unregister_arr)(void *, glui32, char *, gidispatch_rock_t);
- /* Have styles been initialized */
- gboolean style_initialized;
- /* Is widget still displaying windows from last run */
- gboolean needs_reset;
/* *** Platform-dependent Glk library data *** */
/* Flag for functions to find out if they are being called from startup code */
#define CHIMARA_GLK_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE((obj), CHIMARA_TYPE_GLK, ChimaraGlkPrivate))
#define CHIMARA_GLK_USE_PRIVATE(o, n) ChimaraGlkPrivate *n = CHIMARA_GLK_PRIVATE(o)
-G_GNUC_INTERNAL void _chimara_glk_free_nonwindow_private_data(ChimaraGlkPrivate *self);
-
G_END_DECLS
#endif /* __CHIMARA_GLK_PRIVATE_H__ */
PROP_PROTECT,
PROP_DEFAULT_FONT_DESCRIPTION,
PROP_MONOSPACE_FONT_DESCRIPTION,
- PROP_SPACING
+ PROP_SPACING,
};
enum {
priv->css_file = "style.css";
priv->default_styles = g_new0(StyleSet,1);
priv->current_styles = g_new0(StyleSet,1);
+ priv->style_initialized = FALSE;
+ priv->final_message = g_strdup("[ The game has finished ]");
priv->running = FALSE;
priv->program = NULL;
priv->thread = NULL;
priv->event_queue_not_full = g_cond_new();
priv->abort_lock = g_mutex_new();
priv->abort_signalled = FALSE;
+ priv->shutdown_lock = g_mutex_new();
+ priv->shutdown_key_pressed = g_cond_new();
priv->arrange_lock = g_mutex_new();
priv->rearranged = g_cond_new();
priv->needs_rearrange = FALSE;
priv->current_stream = NULL;
priv->stream_list = NULL;
priv->timer_id = 0;
- priv->style_initialized = FALSE;
- priv->needs_reset = FALSE;
priv->in_startup = FALSE;
priv->current_dir = NULL;
}
}
}
-void
-_chimara_glk_free_nonwindow_private_data(ChimaraGlkPrivate *priv)
+static void
+chimara_glk_finalize(GObject *object)
{
+ ChimaraGlk *self = CHIMARA_GLK(object);
+ CHIMARA_GLK_USE_PRIVATE(self, priv);
+
+ /* Free widget properties */
+ pango_font_description_free(priv->default_font_desc);
+ pango_font_description_free(priv->monospace_font_desc);
+ g_free(priv->final_message);
+ /* Free styles */
+ g_hash_table_destroy(priv->default_styles->text_buffer);
+ g_hash_table_destroy(priv->default_styles->text_grid);
+ g_hash_table_destroy(priv->current_styles->text_buffer);
+ g_hash_table_destroy(priv->current_styles->text_grid);
+ priv->style_initialized = FALSE;
+
/* Free the event queue */
g_mutex_lock(priv->event_lock);
g_queue_foreach(priv->event_queue, (GFunc)g_free, NULL);
priv->event_queue = NULL;
g_mutex_unlock(priv->event_lock);
g_mutex_free(priv->event_lock);
-
- /* Free the abort signaling mechanism */
+ /* Free the abort signaling mechanism */
g_mutex_lock(priv->abort_lock);
/* Make sure no other thread is busy with this */
g_mutex_unlock(priv->abort_lock);
g_mutex_free(priv->abort_lock);
priv->abort_lock = NULL;
-
- /* Unref input queues */
- g_async_queue_unref(priv->char_input_queue);
- g_async_queue_unref(priv->line_input_queue);
-
- /* Free styles */
- pango_font_description_free(priv->default_font_desc);
- pango_font_description_free(priv->monospace_font_desc);
-
- g_free(priv->current_dir);
- g_hash_table_destroy(priv->default_styles->text_buffer);
- g_hash_table_destroy(priv->default_styles->text_grid);
- g_hash_table_destroy(priv->current_styles->text_buffer);
- g_hash_table_destroy(priv->current_styles->text_grid);
-}
-
-/* Internal function: main thread version of destroy_windows_below, only more
- DESTRUCTO-MATIC! */
-static void
-trash_windows_recursive(ChimaraGlkPrivate *priv, winid_t win)
-{
- switch(win->type)
- {
- case wintype_Blank:
- case wintype_TextGrid:
- case wintype_TextBuffer:
- gtk_widget_unparent(win->frame);
- break;
-
- case wintype_Pair:
- trash_windows_recursive(priv, win->window_node->children->data);
- trash_windows_recursive(priv, win->window_node->children->next->data);
- break;
-
- default:
- ILLEGAL_PARAM("Unknown window type: %u", win->type);
- return;
- }
- trash_stream_thread_independent(priv, win->window_stream);
- trash_window_thread_independent(priv, win);
-}
-
-void
-_chimara_glk_free_window_private_data(ChimaraGlkPrivate *priv)
-{
- /* Destroy the window tree */
- if(priv->root_window) {
- trash_windows_recursive(priv, priv->root_window->data);
- g_node_destroy(priv->root_window);
- }
-
+ /* Free the shutdown keypress signaling mechanism */
+ g_mutex_lock(priv->shutdown_lock);
+ g_cond_free(priv->shutdown_key_pressed);
+ g_mutex_unlock(priv->shutdown_lock);
+ priv->shutdown_lock = NULL;
/* Free the window arrangement signaling */
g_mutex_lock(priv->arrange_lock);
g_cond_free(priv->rearranged);
g_mutex_unlock(priv->arrange_lock);
g_mutex_free(priv->arrange_lock);
priv->arrange_lock = NULL;
+ /* Unref input queues (this should destroy them since any Glk thread has stopped by now */
+ g_async_queue_unref(priv->char_input_queue);
+ g_async_queue_unref(priv->line_input_queue);
+
+ /* Free other stuff */
+ if(priv->current_dir)
+ g_free(priv->current_dir);
- /* Remove the dispatch callbacks */
- priv->register_obj = NULL;
- priv->unregister_obj = NULL;
- priv->register_arr = NULL;
- priv->unregister_arr = NULL;
-}
-
-static void
-chimara_glk_finalize(GObject *object)
-{
- ChimaraGlk *self = CHIMARA_GLK(object);
- CHIMARA_GLK_USE_PRIVATE(self, priv);
- _chimara_glk_free_nonwindow_private_data(priv);
- _chimara_glk_free_window_private_data(priv);
-
+ /* Chain up to parent */
G_OBJECT_CLASS(chimara_glk_parent_class)->finalize(object);
}
chimara_glk_stopped(ChimaraGlk *self)
{
CHIMARA_GLK_USE_PRIVATE(self, priv);
- printf("stopped signal received\n");
priv->running = FALSE;
/* Free the plugin */
if( priv->program && !g_module_close(priv->program) )
g_warning( "Error closing module: %s", g_module_error() );
+ priv->program = NULL;
}
static void
chimara_glk_started(ChimaraGlk *self)
{
CHIMARA_GLK_USE_PRIVATE(self, priv);
- printf("started signal received\n");
priv->running = TRUE;
}
extern GPrivate *glk_data_key;
g_private_set(glk_data_key, startup->glk_data);
+ /* Acquire the Glk thread's references to the input queues */
g_async_queue_ref(startup->glk_data->char_input_queue);
g_async_queue_ref(startup->glk_data->line_input_queue);
}
/* Run main function */
- g_signal_emit_by_name(startup->glk_data->self, "started");
- (startup->glk_main)();
- g_signal_emit_by_name(startup->glk_data->self, "stopped");
- /* FIXME: hack. should be done by the signal above but for some reason
- * this doesn't work */
- chimara_glk_stopped(startup->glk_data->self);
-
+ glk_main_t glk_main = startup->glk_main;
g_slice_free(struct StartupData, startup);
- return NULL;
+ g_signal_emit_by_name(startup->glk_data->self, "started");
+ glk_main();
+ glk_exit(); /* Run shutdown code in glk_exit() even if glk_main() returns normally */
+ g_assert_not_reached(); /* because glk_exit() calls g_thread_exit() */
+ return NULL;
}
/**
ChimaraGlkPrivate *priv = CHIMARA_GLK_PRIVATE(glk);
struct StartupData *startup = g_slice_new0(struct StartupData);
-
- /* If anything was left over from the previous run, destroy it */
- if(priv->needs_reset) {
- _chimara_glk_free_window_private_data(priv);
- priv->needs_reset = FALSE;
- chimara_glk_init(glk);
- }
/* Open the module to run */
g_assert( g_module_supported() );
g_return_if_fail(glk || CHIMARA_IS_GLK(glk));
CHIMARA_GLK_USE_PRIVATE(glk, priv);
- printf("stopping (%d)...\n", priv->running);
/* Don't do anything if not running a program */
if(!priv->running)
return;
g_mutex_unlock(priv->abort_lock);
/* Stop blocking on the event queue condition */
event_throw(glk, evtype_Abort, NULL, 0, 0);
+ /* Stop blocking on the shutdown key press condition */
+ g_mutex_lock(priv->shutdown_lock);
+ g_cond_signal(priv->shutdown_key_pressed);
+ g_mutex_unlock(priv->shutdown_lock);
}
}
/* Don't do anything if not running a program */
if(!priv->running)
return;
+ /* Unlock GDK mutex, because the Glk program might need to use it for shutdown */
+ gdk_threads_leave();
g_thread_join(priv->thread);
+ gdk_threads_enter();
}
/**
#include "chimara-glk.h"
#include "chimara-glk-private.h"
#include "gi_blorb.h"
+#include "window.h"
G_GNUC_INTERNAL GPrivate *glk_data_key = NULL;
void
glk_exit(void)
{
- shutdown_glk();
+ ChimaraGlkPrivate *glk_data = g_private_get(glk_data_key);
+
+ shutdown_glk_pre();
+
+ /* Find the biggest text buffer window */
+ winid_t win, largewin = NULL;
+ glui32 largearea = 0;
+ for(win = glk_window_iterate(NULL, NULL); win; win = glk_window_iterate(win, NULL)) {
+ if(win->type == wintype_TextBuffer) {
+ glui32 w, h;
+ if(!largewin) {
+ largewin = win;
+ glk_window_get_size(largewin, &w, &h);
+ largearea = w * h;
+ } else {
+ glk_window_get_size(win, &w, &h);
+ if(w * h > largearea) {
+ largewin = win;
+ largearea = w * h;
+ }
+ }
+ }
+ }
+ if(largewin) {
+ glk_set_window(largewin);
+ glk_set_style(style_Alert);
+ glk_put_string("\n");
+ glk_put_string(glk_data->final_message);
+ glk_put_string("\n");
+ flush_window_buffer(largewin);
+ }
+
+ g_mutex_lock(glk_data->shutdown_lock);
+ for(win = glk_window_iterate(NULL, NULL); win; win = glk_window_iterate(win, NULL)) {
+ if(win->type == wintype_TextGrid || win->type == wintype_TextBuffer)
+ g_signal_handler_unblock(win->widget, win->shutdown_keypress_handler);
+ }
+ g_cond_wait(glk_data->shutdown_key_pressed, glk_data->shutdown_lock);
+ g_mutex_unlock(glk_data->shutdown_lock);
+
+ shutdown_glk_post();
+
+ g_signal_emit_by_name(glk_data->self, "stopped");
+
g_thread_exit(NULL);
}
}
}
+/* Helper function: Turn off shutdown key-press-event signal handler */
+static gboolean
+turn_off_handler(GNode *node)
+{
+ winid_t win = node->data;
+ g_signal_handler_block(win->widget, win->shutdown_keypress_handler);
+ return FALSE; /* don't stop */
+}
+
+/* Internal function: Callback for signal key-press-event while waiting for shutdown. */
+gboolean
+on_shutdown_key_press_event(GtkWidget *widget, GdkEventKey *event, winid_t win)
+{
+ ChimaraGlk *glk = CHIMARA_GLK(gtk_widget_get_ancestor(widget, CHIMARA_TYPE_GLK));
+ g_assert(glk);
+ CHIMARA_GLK_USE_PRIVATE(glk, priv);
+
+ /* Turn off all the signal handlers */
+ if(priv->root_window)
+ g_node_traverse(priv->root_window, G_IN_ORDER, G_TRAVERSE_LEAVES, -1, (GNodeTraverseFunc)turn_off_handler, NULL);
+
+ /* Signal the Glk library that it can shut everything down now */
+ g_mutex_lock(priv->shutdown_lock);
+ g_cond_signal(priv->shutdown_key_pressed);
+ g_mutex_unlock(priv->shutdown_lock);
+
+ return TRUE; /* block the event */
+}
+
/* Internal function: General callback for signal key-press-event on a text buffer or text grid window. Used in character input on both text buffers and grids. Blocked when not in use. */
gboolean
on_char_input_key_press_event(GtkWidget *widget, GdkEventKey *event, winid_t win)
#include "event.h"
#include "strio.h"
+G_GNUC_INTERNAL gboolean on_shutdown_key_press_event(GtkWidget *widget, GdkEventKey *event, winid_t win);
G_GNUC_INTERNAL gboolean on_char_input_key_press_event(GtkWidget *widget, GdkEventKey *event, winid_t win);
G_GNUC_INTERNAL gboolean on_line_input_key_press_event(GtkWidget *widget, GdkEventKey *event, winid_t win);
G_GNUC_INTERNAL void after_window_insert_text(GtkTextBuffer *textbuffer, GtkTextIter *location, gchar *text, gint len, winid_t win);
return str;
}
-/* Internal function: stream closing stuff that is safe to call from either the
- main thread or the Glk thread. */
-void
-trash_stream_thread_independent(ChimaraGlkPrivate *glk_data, strid_t str)
-{
- /* Remove the stream from the global stream list */
- glk_data->stream_list = g_list_delete_link(glk_data->stream_list, str->stream_list);
-
- /* If it was the current output stream, set that to NULL */
- if(glk_data->current_stream == str)
- glk_data->current_stream = NULL;
-
- str->magic = MAGIC_FREE;
- g_free(str);
-}
-
/* Internal function: Stuff to do upon closing any type of stream. Call only
from Glk thread. */
void
result->writecount = str->write_count;
}
- trash_stream_thread_independent(glk_data, str);
+ /* Remove the stream from the global stream list */
+ glk_data->stream_list = g_list_delete_link(glk_data->stream_list, str->stream_list);
+
+ /* If it was the current output stream, set that to NULL */
+ if(glk_data->current_stream == str)
+ glk_data->current_stream = NULL;
+
+ str->magic = MAGIC_FREE;
+ g_free(str);
}
/**
G_GNUC_INTERNAL strid_t file_stream_new(frefid_t fileref, glui32 fmode, glui32 rock, gboolean unicode);
G_GNUC_INTERNAL strid_t stream_new_common(glui32 rock);
G_GNUC_INTERNAL void stream_close_common(strid_t str, stream_result_t *result);
-G_GNUC_INTERNAL void trash_stream_thread_independent(ChimaraGlkPrivate *glk_data, strid_t str);
#endif
/* Initialise hyperlink table */
win->hyperlinks = g_hash_table_new_full(g_int_hash, g_direct_equal, g_free, g_object_unref);
-
- return win;
-}
-
-/* Internal function: window closing stuff that is safe to call from either the
- main thread or the Glk thread. */
-void
-trash_window_thread_independent(ChimaraGlkPrivate *glk_data, winid_t win)
-{
- win->magic = MAGIC_FREE;
- g_list_foreach(win->history, (GFunc)g_free, NULL);
- g_list_free(win->history);
-
- g_string_free(win->buffer, TRUE);
- g_hash_table_destroy(win->hyperlinks);
- g_free(win->current_hyperlink);
- g_free(win);
+ return win;
}
/* Internal function: do all the stuff necessary to close a window. Call only
if(destroy_node)
g_node_destroy(win->window_node);
- trash_window_thread_independent(glk_data, win);
+ win->magic = MAGIC_FREE;
+
+ g_list_foreach(win->history, (GFunc)g_free, NULL);
+ g_list_free(win->history);
+
+ g_string_free(win->buffer, TRUE);
+ g_hash_table_destroy(win->hyperlinks);
+ g_free(win->current_hyperlink);
+ g_free(win);
}
/**
g_signal_handler_block(textview, win->char_input_keypress_handler);
win->line_input_keypress_handler = g_signal_connect(textview, "key-press-event", G_CALLBACK(on_line_input_key_press_event), win);
g_signal_handler_block(textview, win->line_input_keypress_handler);
+ win->shutdown_keypress_handler = g_signal_connect(textview, "key-press-event", G_CALLBACK(on_shutdown_key_press_event), win);
+ g_signal_handler_block(textview, win->shutdown_keypress_handler);
}
break;
g_signal_handler_block(textview, win->char_input_keypress_handler);
win->line_input_keypress_handler = g_signal_connect( textview, "key-press-event", G_CALLBACK(on_line_input_key_press_event), win );
g_signal_handler_block(textview, win->line_input_keypress_handler);
-
+ win->shutdown_keypress_handler = g_signal_connect( textview, "key-press-event", G_CALLBACK(on_shutdown_key_press_event), win );
+ g_signal_handler_block(textview, win->shutdown_keypress_handler);
win->insert_text_handler = g_signal_connect_after( textbuffer, "insert-text", G_CALLBACK(after_window_insert_text), win );
g_signal_handler_block(textbuffer, win->insert_text_handler);
-
/* Create an editable tag to indicate uneditable parts of the window
(for line input) */
gtk_text_buffer_create_tag(textbuffer, "uneditable", "editable", FALSE, "editable-set", TRUE, NULL);
gulong line_input_keypress_handler;
gulong insert_text_handler;
gulong tag_event_handler;
+ gulong shutdown_keypress_handler;
/* Window buffer */
GString *buffer;
/* Hyperlinks */
struct hyperlink *current_hyperlink;
};
-G_GNUC_INTERNAL void trash_window_thread_independent(ChimaraGlkPrivate *glk_data, winid_t win);
-
#endif