1 /* licensing and copyright information here */
4 #include <glib/gi18n.h>
6 #include "chimara-glk.h"
7 #include "chimara-glk-private.h"
12 #define CHIMARA_GLK_MIN_WIDTH 0
13 #define CHIMARA_GLK_MIN_HEIGHT 0
15 typedef void (* glk_main_t) (void);
30 static guint chimara_glk_signals[LAST_SIGNAL] = { 0 };
32 G_DEFINE_TYPE(ChimaraGlk, chimara_glk, GTK_TYPE_CONTAINER);
35 chimara_glk_init(ChimaraGlk *self)
37 GTK_WIDGET_SET_FLAGS(GTK_WIDGET(self), GTK_NO_WINDOW);
39 ChimaraGlkPrivate *priv = CHIMARA_GLK_PRIVATE(self);
42 priv->interactive = TRUE;
43 priv->protect = FALSE;
46 priv->event_queue = NULL;
47 priv->event_lock = NULL;
48 priv->event_queue_not_empty = NULL;
49 priv->event_queue_not_full = NULL;
50 priv->abort_lock = NULL;
51 priv->abort_signalled = FALSE;
52 priv->interrupt_handler = NULL;
53 priv->root_window = NULL;
54 priv->fileref_list = NULL;
55 priv->current_stream = NULL;
56 priv->stream_list = NULL;
60 chimara_glk_set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec)
62 ChimaraGlk *glk = CHIMARA_GLK(object);
66 case PROP_INTERACTIVE:
67 chimara_glk_set_interactive(glk, g_value_get_boolean(value));
70 chimara_glk_set_protect(glk, g_value_get_boolean(value));
73 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
78 chimara_glk_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
80 ChimaraGlkPrivate *priv = CHIMARA_GLK_PRIVATE(object);
84 case PROP_INTERACTIVE:
85 g_value_set_boolean(value, priv->interactive);
88 g_value_set_boolean(value, priv->protect);
91 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
96 chimara_glk_finalize(GObject *object)
98 ChimaraGlk *self = CHIMARA_GLK(object);
99 ChimaraGlkPrivate *priv = CHIMARA_GLK_PRIVATE(self);
101 /* Free the event queue */
102 g_mutex_lock(priv->event_lock);
103 g_queue_foreach(priv->event_queue, (GFunc)g_free, NULL);
104 g_queue_free(priv->event_queue);
105 g_cond_free(priv->event_queue_not_empty);
106 g_cond_free(priv->event_queue_not_full);
107 priv->event_queue = NULL;
108 g_mutex_unlock(priv->event_lock);
109 g_mutex_free(priv->event_lock);
111 /* Free the abort signalling mechanism */
112 g_mutex_lock(priv->abort_lock);
113 /* Make sure no other thread is busy with this */
114 g_mutex_unlock(priv->abort_lock);
115 g_mutex_free(priv->abort_lock);
116 priv->abort_lock = NULL;
118 G_OBJECT_CLASS(chimara_glk_parent_class)->finalize(object);
122 chimara_glk_size_request(GtkWidget *widget, GtkRequisition *requisition)
124 g_return_if_fail(widget);
125 g_return_if_fail(requisition);
126 g_return_if_fail(CHIMARA_IS_GLK(widget));
128 ChimaraGlkPrivate *priv = CHIMARA_GLK_PRIVATE(widget);
130 /* For now, just pass the size request on to the root Glk window */
131 if(priv->root_window) {
132 GtkWidget *child = ((winid_t)(priv->root_window->data))->frame;
133 if(GTK_WIDGET_VISIBLE(child))
134 gtk_widget_size_request(child, requisition);
136 requisition->width = CHIMARA_GLK_MIN_WIDTH;
137 requisition->height = CHIMARA_GLK_MIN_HEIGHT;
142 chimara_glk_size_allocate(GtkWidget *widget, GtkAllocation *allocation)
144 g_return_if_fail(widget);
145 g_return_if_fail(allocation);
146 g_return_if_fail(CHIMARA_IS_GLK(widget));
148 ChimaraGlkPrivate *priv = CHIMARA_GLK_PRIVATE(widget);
150 widget->allocation = *allocation;
152 if(priv->root_window) {
153 GtkWidget *child = ((winid_t)(priv->root_window->data))->frame;
154 if(GTK_WIDGET_VISIBLE(child))
155 gtk_widget_size_allocate(child, allocation);
160 chimara_glk_forall(GtkContainer *container, gboolean include_internals,
161 GtkCallback callback, gpointer callback_data)
163 g_return_if_fail(container);
164 g_return_if_fail(CHIMARA_IS_GLK(container));
166 ChimaraGlkPrivate *priv = CHIMARA_GLK_PRIVATE(container);
168 if(priv->root_window) {
169 GtkWidget *child = ((winid_t)(priv->root_window->data))->frame;
170 (*callback)(child, callback_data);
175 chimara_glk_stopped(ChimaraGlk *self)
177 ChimaraGlkPrivate *priv = CHIMARA_GLK_PRIVATE(self);
179 /* Free the plugin */
180 if( priv->program && !g_module_close(priv->program) )
181 g_warning( "Error closing module: %s", g_module_error() );
185 chimara_glk_started(ChimaraGlk *self)
187 /* TODO: Add default signal handler implementation here */
191 chimara_glk_class_init(ChimaraGlkClass *klass)
193 /* Override methods of parent classes */
194 GObjectClass *object_class = G_OBJECT_CLASS(klass);
195 object_class->set_property = chimara_glk_set_property;
196 object_class->get_property = chimara_glk_get_property;
197 object_class->finalize = chimara_glk_finalize;
199 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass);
200 widget_class->size_request = chimara_glk_size_request;
201 widget_class->size_allocate = chimara_glk_size_allocate;
203 GtkContainerClass *container_class = GTK_CONTAINER_CLASS(klass);
204 container_class->forall = chimara_glk_forall;
207 klass->stopped = chimara_glk_stopped;
208 klass->started = chimara_glk_started;
209 chimara_glk_signals[STOPPED] = g_signal_new("stopped",
210 G_OBJECT_CLASS_TYPE(klass), 0,
211 G_STRUCT_OFFSET(ChimaraGlkClass, stopped), NULL, NULL,
212 g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0);
213 chimara_glk_signals[STARTED] = g_signal_new ("started",
214 G_OBJECT_CLASS_TYPE (klass), 0,
215 G_STRUCT_OFFSET(ChimaraGlkClass, started), NULL, NULL,
216 g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0);
220 pspec = g_param_spec_boolean("interactive", _("Interactive"),
221 _("Whether user input is expected in the Glk program"),
223 G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_LAX_VALIDATION |
224 G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB);
225 g_object_class_install_property(object_class, PROP_INTERACTIVE, pspec);
226 pspec = g_param_spec_boolean("protect", _("Protected"),
227 _("Whether the Glk program is barred from doing file operations"),
229 G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_LAX_VALIDATION |
230 G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB);
231 g_object_class_install_property(object_class, PROP_PROTECT, pspec);
234 g_type_class_add_private(klass, sizeof(ChimaraGlkPrivate));
237 /* PUBLIC FUNCTIONS */
242 * Creates and initializes a new #ChimaraGlk widget.
244 * Return value: a #ChimaraGlk widget, with a floating reference.
247 chimara_glk_new(void)
249 ChimaraGlk *self = CHIMARA_GLK(g_object_new(CHIMARA_TYPE_GLK, NULL));
250 ChimaraGlkPrivate *priv = CHIMARA_GLK_PRIVATE(self);
252 priv->event_queue = g_queue_new();
253 priv->event_lock = g_mutex_new();
254 priv->event_queue_not_empty = g_cond_new();
255 priv->event_queue_not_full = g_cond_new();
256 priv->abort_lock = g_mutex_new();
258 return GTK_WIDGET(self);
262 * chimara_glk_set_interactive:
263 * @glk: a #ChimaraGlk widget
264 * @interactive: whether the widget should expect user input
266 * Sets the #ChimaraGlk:interactive property of @glk. A Glk widget is normally
267 * interactive, but in non-interactive mode, keyboard and mouse input is ignored
268 * and the Glk program is controlled by chimara_glk_feed_text(). "More" prompts
269 * when a lot of text is printed to a text buffer are also disabled. This is
270 * typically used when you wish to control an interpreter program by feeding it
271 * a predefined list of commands.
274 chimara_glk_set_interactive(ChimaraGlk *glk, gboolean interactive)
276 g_return_if_fail(glk || CHIMARA_IS_GLK(glk));
278 ChimaraGlkPrivate *priv = CHIMARA_GLK_PRIVATE(glk);
279 priv->interactive = interactive;
283 * chimara_glk_get_interactive:
284 * @glk: a #ChimaraGlk widget
286 * Returns whether @glk is interactive (expecting user input). See
287 * chimara_glk_set_interactive().
289 * Return value: %TRUE if @glk is interactive.
292 chimara_glk_get_interactive(ChimaraGlk *glk)
294 g_return_val_if_fail(glk || CHIMARA_IS_GLK(glk), FALSE);
296 ChimaraGlkPrivate *priv = CHIMARA_GLK_PRIVATE(glk);
297 return priv->interactive;
301 * chimara_glk_set_protect:
302 * @glk: a #ChimaraGlk widget
303 * @protect: whether the widget should allow the Glk program to do file
306 * Sets the #ChimaraGlk:protect property of @glk. In protect mode, the Glk
307 * program is not allowed to do file operations.
310 chimara_glk_set_protect(ChimaraGlk *glk, gboolean protect)
312 g_return_if_fail(glk || CHIMARA_IS_GLK(glk));
314 ChimaraGlkPrivate *priv = CHIMARA_GLK_PRIVATE(glk);
315 priv->protect = protect;
319 * chimara_glk_get_protect:
320 * @glk: a #ChimaraGlk widget
322 * Returns whether @glk is in protect mode (banned from doing file operations).
323 * See chimara_glk_set_protect().
325 * Return value: %TRUE if @glk is in protect mode.
328 chimara_glk_get_protect(ChimaraGlk *glk)
330 g_return_val_if_fail(glk || CHIMARA_IS_GLK(glk), FALSE);
332 ChimaraGlkPrivate *priv = CHIMARA_GLK_PRIVATE(glk);
333 return priv->protect;
336 /* glk_enter() is the actual function called in the new thread in which glk_main() runs. */
338 glk_enter(gpointer glk_main)
340 extern ChimaraGlkPrivate *glk_data;
341 g_signal_emit_by_name(glk_data->self, "started");
342 ((glk_main_t)glk_main)();
343 g_signal_emit_by_name(glk_data->self, "stopped");
349 * @glk: a #ChimaraGlk widget
350 * @plugin: path to a plugin module compiled with glk.h
351 * @error: location to store a #GError, or %NULL
353 * Opens a Glk program compiled as a plugin and runs its glk_main() function in
354 * a separate thread. On failure, returns %FALSE and sets @error.
356 * Return value: %TRUE if the Glk program was started successfully.
359 chimara_glk_run(ChimaraGlk *glk, gchar *plugin, GError **error)
361 g_return_val_if_fail(glk || CHIMARA_IS_GLK(glk), FALSE);
362 g_return_val_if_fail(plugin, FALSE);
364 ChimaraGlkPrivate *priv = CHIMARA_GLK_PRIVATE(glk);
366 /* Open the module to run */
368 g_assert( g_module_supported() );
369 priv->program = g_module_open(plugin, G_MODULE_BIND_LAZY);
373 g_warning( "Error opening module: %s", g_module_error() );
376 if( !g_module_symbol(priv->program, "glk_main", (gpointer *) &glk_main) )
378 g_warning( "Error finding glk_main(): %s", g_module_error() );
382 extern ChimaraGlkPrivate *glk_data;
383 /* Set the thread's private data */
384 /* TODO: Do this with a GPrivate */
387 /* Run in a separate thread */
388 priv->thread = g_thread_create(glk_enter, glk_main, TRUE, error);
390 return !(priv->thread == NULL);
395 * @glk: a #ChimaraGlk widget
397 * Signals the Glk program running in @glk to abort. Note that if the program is
398 * caught in an infinite loop in which glk_tick() is not called, this may not
402 chimara_glk_stop(ChimaraGlk *glk)
404 g_return_if_fail(glk || CHIMARA_IS_GLK(glk));
405 /* TODO: check if glk is actually running a program */
411 * @glk: a #ChimaraGlk widget
413 * Holds up the main thread and waits for the Glk program running in @glk to
417 chimara_glk_wait(ChimaraGlk *glk)
419 g_return_if_fail(glk || CHIMARA_IS_GLK(glk));
421 ChimaraGlkPrivate *priv = CHIMARA_GLK_PRIVATE(glk);
422 g_thread_join(priv->thread);