Fixed issue #1. Probably needs some more thorough testing.
[projects/chimara/chimara.git] / src / chimara-glk.c
1 /* licensing and copyright information here */
2
3 #include <math.h>
4 #include <gtk/gtk.h>
5 #include <glib/gi18n.h>
6 #include <gmodule.h>
7 #include <pango/pango.h>
8 #include "chimara-glk.h"
9 #include "chimara-glk-private.h"
10 #include "glk.h"
11 #include "abort.h"
12 #include "window.h"
13
14 #define CHIMARA_GLK_MIN_WIDTH 0
15 #define CHIMARA_GLK_MIN_HEIGHT 0
16
17 /**
18  * SECTION:chimara-glk
19  * @short_description: Widget which executes a Glk program
20  * @stability: Unstable
21  * @include: chimara/chimara-glk.h
22  * 
23  * The ChimaraGlk widget opens and runs a Glk program. The program must be
24  * compiled as a plugin module, with a function <function>glk_main()</function>
25  * that the Glk library can hook into.
26  *
27  * On Linux systems, this is a file with a name like 
28  * <filename>plugin.so</filename>. For portability, you can use libtool and 
29  * automake:
30  * <informalexample><programlisting>
31  * pkglib_LTLIBRARIES = plugin.la
32  * plugin_la_SOURCES = plugin.c foo.c bar.c
33  * plugin_la_LDFLAGS = -module -shared -avoid-version -export-symbols-regex "^glk_main$$"
34  * </programlisting></informalexample>
35  * This will produce <filename>plugin.la</filename> which is a text file 
36  * containing the correct plugin file to open (see the relevant section of the
37  * <ulink 
38  * url="http://www.gnu.org/software/libtool/manual/html_node/Finding-the-dlname.html">
39  * Libtool manual</ulink>).
40  */
41
42 typedef void (* glk_main_t) (void);
43
44 enum {
45     PROP_0,
46     PROP_INTERACTIVE,
47     PROP_PROTECT,
48         PROP_DEFAULT_FONT_DESCRIPTION,
49         PROP_MONOSPACE_FONT_DESCRIPTION
50 };
51
52 enum {
53         STOPPED,
54         STARTED,
55
56         LAST_SIGNAL
57 };
58
59 static guint chimara_glk_signals[LAST_SIGNAL] = { 0 };
60
61 G_DEFINE_TYPE(ChimaraGlk, chimara_glk, GTK_TYPE_CONTAINER);
62
63 static void
64 chimara_glk_init(ChimaraGlk *self)
65 {
66     GTK_WIDGET_SET_FLAGS(GTK_WIDGET(self), GTK_NO_WINDOW);
67
68     ChimaraGlkPrivate *priv = CHIMARA_GLK_PRIVATE(self);
69     
70     priv->self = self;
71     priv->interactive = TRUE;
72     priv->protect = FALSE;
73         priv->default_font_desc = pango_font_description_from_string("Sans");
74         priv->monospace_font_desc = pango_font_description_from_string("Monospace");
75     priv->program = NULL;
76     priv->thread = NULL;
77     priv->event_queue = NULL;
78     priv->event_lock = NULL;
79     priv->event_queue_not_empty = NULL;
80     priv->event_queue_not_full = NULL;
81     priv->abort_lock = NULL;
82     priv->abort_signalled = FALSE;
83     priv->interrupt_handler = NULL;
84     priv->root_window = NULL;
85     priv->fileref_list = NULL;
86     priv->current_stream = NULL;
87     priv->stream_list = NULL;
88 }
89
90 static void
91 chimara_glk_set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec)
92 {
93     ChimaraGlk *glk = CHIMARA_GLK(object);
94     
95     switch(prop_id) 
96     {
97         case PROP_INTERACTIVE:
98             chimara_glk_set_interactive( glk, g_value_get_boolean(value) );
99             break;
100         case PROP_PROTECT:
101             chimara_glk_set_protect( glk, g_value_get_boolean(value) );
102             break;
103                 case PROP_DEFAULT_FONT_DESCRIPTION:
104                         chimara_glk_set_default_font_description( glk, (PangoFontDescription *)g_value_get_pointer(value) );
105                         break;
106                 case PROP_MONOSPACE_FONT_DESCRIPTION:
107                         chimara_glk_set_monospace_font_description( glk, (PangoFontDescription *)g_value_get_pointer(value) );
108                         break;
109         default:
110             G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
111     }
112 }
113
114 static void
115 chimara_glk_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
116 {
117     ChimaraGlkPrivate *priv = CHIMARA_GLK_PRIVATE(object);
118     
119     switch(prop_id)
120     {
121         case PROP_INTERACTIVE:
122             g_value_set_boolean(value, priv->interactive);
123             break;
124         case PROP_PROTECT:
125             g_value_set_boolean(value, priv->protect);
126             break;
127                 case PROP_DEFAULT_FONT_DESCRIPTION:
128                         g_value_set_pointer(value, priv->default_font_desc);
129                         break;
130                 case PROP_MONOSPACE_FONT_DESCRIPTION:
131                         g_value_set_pointer(value, priv->monospace_font_desc);
132                         break;
133         default:
134             G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
135     }
136 }
137
138 static void
139 chimara_glk_finalize(GObject *object)
140 {
141     ChimaraGlk *self = CHIMARA_GLK(object);
142     ChimaraGlkPrivate *priv = CHIMARA_GLK_PRIVATE(self);
143     
144     /* Free the event queue */
145     g_mutex_lock(priv->event_lock);
146         g_queue_foreach(priv->event_queue, (GFunc)g_free, NULL);
147         g_queue_free(priv->event_queue);
148         g_cond_free(priv->event_queue_not_empty);
149         g_cond_free(priv->event_queue_not_full);
150         priv->event_queue = NULL;
151         g_mutex_unlock(priv->event_lock);
152         g_mutex_free(priv->event_lock);
153         
154         /* Free the abort signalling mechanism */
155         g_mutex_lock(priv->abort_lock);
156         /* Make sure no other thread is busy with this */
157         g_mutex_unlock(priv->abort_lock);
158         g_mutex_free(priv->abort_lock);
159         priv->abort_lock = NULL;
160
161         /* Free private data */
162         pango_font_description_free(priv->default_font_desc);
163         pango_font_description_free(priv->monospace_font_desc);
164         
165     G_OBJECT_CLASS(chimara_glk_parent_class)->finalize(object);
166 }
167
168 /* Internal function: Recursively get the Glk window tree's size request */
169 static void
170 request_recurse(winid_t win, GtkRequisition *requisition)
171 {
172         if(win->type == wintype_Pair)
173         {
174                 /* Get children's size requests */
175                 GtkRequisition child1, child2;
176                 request_recurse(win->window_node->children->data, &child1);
177                 request_recurse(win->window_node->children->next->data, &child2);
178                 
179                 /* If the split is fixed, get the size of the fixed child */
180                 if((win->split_method & winmethod_DivisionMask) == winmethod_Fixed)
181                 {
182                         switch(win->split_method & winmethod_DirMask)
183                         {
184                                 case winmethod_Left:
185                                         child1.width = win->constraint_size * win->key_window->unit_width;
186                                         break;
187                                 case winmethod_Right:
188                                         child2.width = win->constraint_size * win->key_window->unit_width;
189                                         break;
190                                 case winmethod_Above:
191                                         child1.height = win->constraint_size * win->key_window->unit_height;
192                                         break;
193                                 case winmethod_Below:
194                                         child2.height = win->constraint_size * win->key_window->unit_height;
195                                         break;
196                         }
197                 }
198                 
199                 /* Add the children's requests */
200                 switch(win->split_method & winmethod_DirMask)
201                 {
202                         case winmethod_Left:
203                         case winmethod_Right:
204                                 requisition->width = child1.width + child2.width;
205                                 requisition->height = MAX(child1.height, child2.height);
206                                 break;
207                         case winmethod_Above:
208                         case winmethod_Below:
209                                 requisition->width = MAX(child1.width, child2.width);
210                                 requisition->height = child1.height + child2.height;
211                                 break;
212                 }
213         }
214         
215         /* For non-pair windows, just use the size that GTK requests */
216         else
217                 gtk_widget_size_request(win->frame, requisition);
218 }
219
220 /* Overrides gtk_widget_size_request */
221 static void
222 chimara_glk_size_request(GtkWidget *widget, GtkRequisition *requisition)
223 {
224     g_return_if_fail(widget);
225     g_return_if_fail(requisition);
226     g_return_if_fail(CHIMARA_IS_GLK(widget));
227     
228     ChimaraGlkPrivate *priv = CHIMARA_GLK_PRIVATE(widget);
229     
230     /* For now, just pass the size request on to the root Glk window */
231     if(priv->root_window)
232                 request_recurse(priv->root_window->data, requisition);
233         else {
234         requisition->width = CHIMARA_GLK_MIN_WIDTH;
235         requisition->height = CHIMARA_GLK_MIN_HEIGHT;
236     }
237 }
238
239 /* Recursively give the Glk windows their allocated space */
240 static void
241 allocate_recurse(winid_t win, GtkAllocation *allocation)
242 {
243         if(win->type == wintype_Pair)
244         {
245                 GtkAllocation child1, child2;
246                 child1.x = allocation->x;
247                 child1.y = allocation->y;
248                 
249                 if((win->split_method & winmethod_DivisionMask) == winmethod_Fixed)
250                 {
251                         switch(win->split_method & winmethod_DirMask)
252                         {
253                                 case winmethod_Left:
254                                         child1.width = win->constraint_size * win->key_window->unit_width;
255                                         if(child1.width > allocation->width)
256                                                 child1.width = allocation->width;
257                                         break;
258                                 case winmethod_Right:
259                                         child2.width = win->constraint_size * win->key_window->unit_width;
260                                         if(child2.width > allocation->width)
261                                                 child2.width = allocation->width;
262                                         break;
263                                 case winmethod_Above:
264                                         child1.height = win->constraint_size * win->key_window->unit_height;
265                                         if(child1.height > allocation->height)
266                                                 child1.height = allocation->height;
267                                         break;
268                                 case winmethod_Below:
269                                         child2.height = win->constraint_size * win->key_window->unit_height;
270                                         if(child2.height > allocation->height)
271                                                 child2.height = allocation->height;
272                                         break;
273                         }
274                 }
275                 else /* proportional */
276                 {
277                         switch(win->split_method & winmethod_DirMask)
278                         {
279                                 case winmethod_Left:
280                                         child1.width = (glui32)ceil((win->constraint_size / 100.0) * allocation->width);
281                                         break;
282                                 case winmethod_Right:
283                                         child2.width = (glui32)ceil((win->constraint_size / 100.0) * allocation->width);
284                                         break;
285                                 case winmethod_Above:
286                                         child1.height = (glui32)ceil((win->constraint_size / 100.0) * allocation->height);
287                                         break;
288                                 case winmethod_Below:
289                                         child2.height = (glui32)ceil((win->constraint_size / 100.0) * allocation->height);
290                                         break;
291                         }
292                 }
293                 
294                 /* Fill in the rest of the size requisitions according to the child specified above */
295                 switch(win->split_method & winmethod_DirMask)
296                 {
297                         case winmethod_Left:
298                                 child2.width = allocation->width - child1.width;
299                                 child2.x = child1.x + child1.width;
300                                 child2.y = child1.y;
301                                 child1.height = child2.height = allocation->height;
302                                 break;
303                         case winmethod_Right:
304                                 child1.width = allocation->width - child2.width;
305                                 child2.x = child1.x + child1.width;
306                                 child2.y = child1.y;
307                                 child1.height = child2.height = allocation->height;
308                                 break;
309                         case winmethod_Above:
310                                 child2.height = allocation->height - child1.height;
311                                 child2.x = child1.x;
312                                 child2.y = child1.y + child1.height;
313                                 child1.width = child2.width = allocation->width;
314                                 break;
315                         case winmethod_Below:
316                                 child1.height = allocation->height - child2.height;
317                                 child2.x = child1.x;
318                                 child2.y = child1.y + child1.height;
319                                 child1.width = child2.width = allocation->width;
320                                 break;
321                 }
322                 
323                 /* Recurse */
324                 allocate_recurse(win->window_node->children->data, &child1);
325                 allocate_recurse(win->window_node->children->next->data, &child2);
326         }
327         
328         /* For non-pair windows, just give them the size */
329         else
330                 gtk_widget_size_allocate(win->frame, allocation);
331 }
332
333 /* Overrides gtk_widget_size_allocate */
334 static void
335 chimara_glk_size_allocate(GtkWidget *widget, GtkAllocation *allocation)
336 {
337     g_return_if_fail(widget);
338     g_return_if_fail(allocation);
339     g_return_if_fail(CHIMARA_IS_GLK(widget));
340     
341     ChimaraGlkPrivate *priv = CHIMARA_GLK_PRIVATE(widget);
342     
343     widget->allocation = *allocation;
344             
345     if(priv->root_window)
346                 allocate_recurse(priv->root_window->data, allocation);
347 }
348
349 /* Recursively invoke callback() on the GtkWidget of each non-pair window in the tree */
350 static void
351 forall_recurse(winid_t win, GtkCallback callback, gpointer callback_data)
352 {
353         if(win->type == wintype_Pair)
354         {
355                 forall_recurse(win->window_node->children->data, callback, callback_data);
356                 forall_recurse(win->window_node->children->next->data, callback, callback_data);
357         }
358         else
359                 (*callback)(win->frame, callback_data);
360 }
361
362 /* Overrides gtk_container_forall */
363 static void
364 chimara_glk_forall(GtkContainer *container, gboolean include_internals, GtkCallback callback, gpointer callback_data)
365 {
366     g_return_if_fail(container);
367     g_return_if_fail(CHIMARA_IS_GLK(container));
368     
369     ChimaraGlkPrivate *priv = CHIMARA_GLK_PRIVATE(container);
370     
371         /* All the children are "internal" */
372         if(!include_internals)
373                 return;
374         
375     if(priv->root_window)
376                 forall_recurse(priv->root_window->data, callback, callback_data);
377 }
378
379 static void
380 chimara_glk_stopped(ChimaraGlk *self)
381 {
382     ChimaraGlkPrivate *priv = CHIMARA_GLK_PRIVATE(self);
383
384     /* Free the plugin */
385         if( priv->program && !g_module_close(priv->program) )
386             g_warning( "Error closing module: %s", g_module_error() );
387 }
388
389 static void
390 chimara_glk_started(ChimaraGlk *self)
391 {
392         /* TODO: Add default signal handler implementation here */
393 }
394
395 /* G_PARAM_STATIC_STRINGS only appeared in GTK 2.13.0 */
396 #ifndef G_PARAM_STATIC_STRINGS
397 #define G_PARAM_STATIC_STRINGS (G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB)
398 #endif
399
400 static void
401 chimara_glk_class_init(ChimaraGlkClass *klass)
402 {
403     /* Override methods of parent classes */
404     GObjectClass *object_class = G_OBJECT_CLASS(klass);
405     object_class->set_property = chimara_glk_set_property;
406     object_class->get_property = chimara_glk_get_property;
407     object_class->finalize = chimara_glk_finalize;
408     
409     GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass);
410     widget_class->size_request = chimara_glk_size_request;
411     widget_class->size_allocate = chimara_glk_size_allocate;
412
413     GtkContainerClass *container_class = GTK_CONTAINER_CLASS(klass);
414     container_class->forall = chimara_glk_forall;
415
416     /* Signals */
417     klass->stopped = chimara_glk_stopped;
418     klass->started = chimara_glk_started;
419     /**
420      * ChimaraGlk::stopped:
421      * @glk: The widget that received the signal
422      *
423      * The ::stopped signal is emitted when the a Glk program finishes
424      * executing in the widget, whether it ended normally, or was interrupted.
425      */ 
426     chimara_glk_signals[STOPPED] = g_signal_new("stopped", 
427         G_OBJECT_CLASS_TYPE(klass), 0, 
428         G_STRUCT_OFFSET(ChimaraGlkClass, stopped), NULL, NULL,
429                 g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0);
430         /**
431          * ChimaraGlk::started:
432          * @glk: The widget that received the signal
433          *
434          * The ::started signal is emitted when a Glk program starts executing in
435          * the widget.
436          */
437         chimara_glk_signals[STARTED] = g_signal_new ("started",
438                 G_OBJECT_CLASS_TYPE (klass), 0,
439                 G_STRUCT_OFFSET(ChimaraGlkClass, started), NULL, NULL,
440                 g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0);
441
442     /* Properties */
443     GParamSpec *pspec;
444     pspec = g_param_spec_boolean("interactive", _("Interactive"),
445         _("Whether user input is expected in the Glk program"),
446         TRUE,
447         G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_LAX_VALIDATION | G_PARAM_STATIC_STRINGS);
448     /**
449      * ChimaraGlk:interactive:
450      *
451      * Sets whether the widget is interactive. A Glk widget is normally 
452      * interactive, but in non-interactive mode, keyboard and mouse input are 
453      * ignored and the Glk program is controlled by chimara_glk_feed_text(). 
454      * <quote>More</quote> prompts when a lot of text is printed to a text 
455          * buffer are also disabled. This is typically used when you wish to control
456          * an interpreter program by feeding it a predefined list of commands.
457      */
458     g_object_class_install_property(object_class, PROP_INTERACTIVE, pspec);
459     pspec = g_param_spec_boolean("protect", _("Protected"),
460         _("Whether the Glk program is barred from doing file operations"),
461         FALSE,
462         G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_LAX_VALIDATION | G_PARAM_STATIC_STRINGS);
463     /**
464      * ChimaraGlk:protect:
465      *
466      * Sets whether the Glk program is allowed to do file operations. In protect
467      * mode, all file operations will fail.
468      */
469     g_object_class_install_property(object_class, PROP_PROTECT, pspec);
470     pspec = g_param_spec_pointer("default-font-description", _("Default Font"),
471                 _("Font description of the default proportional font"),
472                 G_PARAM_READWRITE | G_PARAM_LAX_VALIDATION | G_PARAM_STATIC_STRINGS);
473         /* We can't use G_PARAM_CONSTRUCT on these because then the constructor will
474          initialize them with NULL */
475         /**
476          * ChimaraGlk:default-font-description:
477          * 
478          * Pointer to a #PangoFontDescription describing the default proportional 
479          * font, to be used in text buffer windows for example.
480          *
481          * Default value: font description created from the string 
482          * <quote>Sans</quote>
483          */
484         g_object_class_install_property(object_class, PROP_DEFAULT_FONT_DESCRIPTION, pspec);
485         pspec = g_param_spec_pointer("monospace-font-description", _("Monospace Font"),
486                 _("Font description of the default monospace font"),
487                 G_PARAM_READWRITE | G_PARAM_LAX_VALIDATION | G_PARAM_STATIC_STRINGS);
488         /**
489          * ChimaraGlk:monospace-font-description:
490          *
491          * Pointer to a #PangoFontDescription describing the default monospace font,
492          * to be used in text grid windows and #style_Preformatted, for example.
493          *
494          * Default value: font description created from the string 
495          * <quote>Monospace</quote>
496          */
497         g_object_class_install_property(object_class, PROP_MONOSPACE_FONT_DESCRIPTION, pspec);
498     /* Private data */
499     g_type_class_add_private(klass, sizeof(ChimaraGlkPrivate));
500 }
501
502 /* PUBLIC FUNCTIONS */
503
504 /**
505  * chimara_glk_new:
506  *
507  * Creates and initializes a new #ChimaraGlk widget.
508  *
509  * Return value: a #ChimaraGlk widget, with a floating reference.
510  */
511 GtkWidget *
512 chimara_glk_new(void)
513 {
514     ChimaraGlk *self = CHIMARA_GLK(g_object_new(CHIMARA_TYPE_GLK, NULL));
515     ChimaraGlkPrivate *priv = CHIMARA_GLK_PRIVATE(self);
516     
517     priv->event_queue = g_queue_new();
518     priv->event_lock = g_mutex_new();
519     priv->event_queue_not_empty = g_cond_new();
520     priv->event_queue_not_full = g_cond_new();
521     priv->abort_lock = g_mutex_new();
522     
523     return GTK_WIDGET(self);
524 }
525
526 /**
527  * chimara_glk_set_interactive:
528  * @glk: a #ChimaraGlk widget
529  * @interactive: whether the widget should expect user input
530  *
531  * Sets the #ChimaraGlk:interactive property of @glk. 
532  */
533 void 
534 chimara_glk_set_interactive(ChimaraGlk *glk, gboolean interactive)
535 {
536     g_return_if_fail(glk || CHIMARA_IS_GLK(glk));
537     
538     ChimaraGlkPrivate *priv = CHIMARA_GLK_PRIVATE(glk);
539     priv->interactive = interactive;
540 }
541
542 /**
543  * chimara_glk_get_interactive:
544  * @glk: a #ChimaraGlk widget
545  *
546  * Returns whether @glk is interactive (expecting user input). See 
547  * #ChimaraGlk:interactive.
548  *
549  * Return value: %TRUE if @glk is interactive.
550  */
551 gboolean 
552 chimara_glk_get_interactive(ChimaraGlk *glk)
553 {
554     g_return_val_if_fail(glk || CHIMARA_IS_GLK(glk), FALSE);
555     
556     ChimaraGlkPrivate *priv = CHIMARA_GLK_PRIVATE(glk);
557     return priv->interactive;
558 }
559
560 /**
561  * chimara_glk_set_protect:
562  * @glk: a #ChimaraGlk widget
563  * @protect: whether the widget should allow the Glk program to do file 
564  * operations
565  *
566  * Sets the #ChimaraGlk:protect property of @glk. In protect mode, the Glk 
567  * program is not allowed to do file operations.
568  */
569 void 
570 chimara_glk_set_protect(ChimaraGlk *glk, gboolean protect)
571 {
572     g_return_if_fail(glk || CHIMARA_IS_GLK(glk));
573     
574     ChimaraGlkPrivate *priv = CHIMARA_GLK_PRIVATE(glk);
575     priv->protect = protect;
576 }
577
578 /**
579  * chimara_glk_get_protect:
580  * @glk: a #ChimaraGlk widget
581  *
582  * Returns whether @glk is in protect mode (banned from doing file operations).
583  * See #ChimaraGlk:protect.
584  *
585  * Return value: %TRUE if @glk is in protect mode.
586  */
587 gboolean 
588 chimara_glk_get_protect(ChimaraGlk *glk)
589 {
590     g_return_val_if_fail(glk || CHIMARA_IS_GLK(glk), FALSE);
591     
592     ChimaraGlkPrivate *priv = CHIMARA_GLK_PRIVATE(glk);
593     return priv->protect;
594 }
595
596 /**
597  * chimara_glk_set_default_font_description:
598  * @glk: a #ChimaraGlk widget
599  * @font: a #PangoFontDescription
600  *
601  * Sets @glk's default proportional font. See 
602  * #ChimaraGlk:default-font-description.
603  */
604 void 
605 chimara_glk_set_default_font_description(ChimaraGlk *glk, PangoFontDescription *font)
606 {
607         g_return_if_fail(glk || CHIMARA_IS_GLK(glk));
608         g_return_if_fail(font);
609         
610         ChimaraGlkPrivate *priv = CHIMARA_GLK_PRIVATE(glk);
611         pango_font_description_free(priv->default_font_desc);
612         priv->default_font_desc = pango_font_description_copy(font);
613         
614         /* TODO: Apply the font description to all the windows and recalculate the sizes */
615 }
616
617 /**
618  * chimara_glk_set_default_font_string:
619  * @glk: a #ChimaraGlk widget
620  * @font: string representation of a font description
621  *
622  * Sets @glk's default proportional font according to the string @font, which
623  * must be a string in the form <quote><replaceable>FAMILY-LIST</replaceable> 
624  * [<replaceable>STYLE-OPTIONS</replaceable>] 
625  * [<replaceable>SIZE</replaceable>]</quote>, such as <quote>Charter,Utopia 
626  * Italic 12</quote> or <quote>Sans</quote>. See 
627  * #ChimaraGlk:default-font-description.
628  */
629 void 
630 chimara_glk_set_default_font_string(ChimaraGlk *glk, const gchar *font)
631 {
632         g_return_if_fail(glk || CHIMARA_IS_GLK(glk));
633         g_return_if_fail(font || *font);
634         
635         PangoFontDescription *fontdesc = pango_font_description_from_string(font);
636         g_return_if_fail(fontdesc);
637         
638         ChimaraGlkPrivate *priv = CHIMARA_GLK_PRIVATE(glk);
639         pango_font_description_free(priv->default_font_desc);
640         priv->default_font_desc = fontdesc;
641         
642         /* TODO: Apply the font description to all the windows and recalculate the sizes */
643 }
644         
645 /**
646  * chimara_glk_get_default_font_description:
647  * 
648  * Returns @glk's default proportional font.
649  *
650  * Return value: a newly-allocated #PangoFontDescription which must be freed
651  * using pango_font_description_free(), or %NULL on error.
652  */
653 PangoFontDescription *
654 chimara_glk_get_default_font_description(ChimaraGlk *glk)
655 {
656         g_return_val_if_fail(glk || CHIMARA_IS_GLK(glk), NULL);
657         
658         ChimaraGlkPrivate *priv = CHIMARA_GLK_PRIVATE(glk);
659         return pango_font_description_copy(priv->default_font_desc);
660 }
661
662 /**
663  * chimara_glk_set_monospace_font_description:
664  * @glk: a #ChimaraGlk widget
665  * @font: a #PangoFontDescription
666  *
667  * Sets @glk's default monospace font. See 
668  * #ChimaraGlk:monospace-font-description.
669  */
670 void 
671 chimara_glk_set_monospace_font_description(ChimaraGlk *glk, PangoFontDescription *font)
672 {
673         g_return_if_fail(glk || CHIMARA_IS_GLK(glk));
674         g_return_if_fail(font);
675         
676         ChimaraGlkPrivate *priv = CHIMARA_GLK_PRIVATE(glk);
677         pango_font_description_free(priv->monospace_font_desc);
678         priv->monospace_font_desc = pango_font_description_copy(font);
679         
680         /* TODO: Apply the font description to all the windows and recalculate the sizes */
681 }
682
683 /**
684  * chimara_glk_set_monospace_font_string:
685  * @glk: a #ChimaraGlk widget
686  * @font: string representation of a font description
687  *
688  * Sets @glk's default monospace font according to the string @font, which must
689  * be a string in the form <quote><replaceable>FAMILY-LIST</replaceable> 
690  * [<replaceable>STYLE-OPTIONS</replaceable>] 
691  * [<replaceable>SIZE</replaceable>]</quote>, such as <quote>Courier 
692  * Bold 12</quote> or <quote>Monospace</quote>. See 
693  * #ChimaraGlk:monospace-font-description.
694  */
695 void 
696 chimara_glk_set_monospace_font_string(ChimaraGlk *glk, const gchar *font)
697 {
698         g_return_if_fail(glk || CHIMARA_IS_GLK(glk));
699         g_return_if_fail(font || *font);
700         
701         PangoFontDescription *fontdesc = pango_font_description_from_string(font);
702         g_return_if_fail(fontdesc);
703         
704         ChimaraGlkPrivate *priv = CHIMARA_GLK_PRIVATE(glk);
705         pango_font_description_free(priv->monospace_font_desc);
706         priv->monospace_font_desc = fontdesc;
707         
708         /* TODO: Apply the font description to all the windows and recalculate the sizes */
709 }
710         
711 /**
712  * chimara_glk_get_monospace_font_description:
713  * 
714  * Returns @glk's default monospace font.
715  *
716  * Return value: a newly-allocated #PangoFontDescription which must be freed
717  * using pango_font_description_free(), or %NULL on error.
718  */
719 PangoFontDescription *
720 chimara_glk_get_monospace_font_description(ChimaraGlk *glk)
721 {
722         g_return_val_if_fail(glk || CHIMARA_IS_GLK(glk), NULL);
723         
724         ChimaraGlkPrivate *priv = CHIMARA_GLK_PRIVATE(glk);
725         return pango_font_description_copy(priv->monospace_font_desc);
726 }
727
728 /* glk_enter() is the actual function called in the new thread in which glk_main() runs.  */
729 static gpointer
730 glk_enter(gpointer glk_main)
731 {
732     extern ChimaraGlkPrivate *glk_data;
733     g_signal_emit_by_name(glk_data->self, "started");
734         ((glk_main_t)glk_main)();
735         g_signal_emit_by_name(glk_data->self, "stopped");
736         return NULL;
737 }
738
739 /**
740  * chimara_glk_run:
741  * @glk: a #ChimaraGlk widget
742  * @plugin: path to a plugin module compiled with <filename 
743  * class="header">glk.h</filename>
744  * @error: location to store a <link linkend="glib-GError">GError</link>, or 
745  * %NULL
746  *
747  * Opens a Glk program compiled as a plugin and runs its glk_main() function in
748  * a separate thread. On failure, returns %FALSE and sets @error.
749  *
750  * Return value: %TRUE if the Glk program was started successfully.
751  */
752 gboolean
753 chimara_glk_run(ChimaraGlk *glk, gchar *plugin, GError **error)
754 {
755     g_return_val_if_fail(glk || CHIMARA_IS_GLK(glk), FALSE);
756     g_return_val_if_fail(plugin, FALSE);
757     
758     ChimaraGlkPrivate *priv = CHIMARA_GLK_PRIVATE(glk);
759     
760     /* Open the module to run */
761     glk_main_t glk_main;
762     g_assert( g_module_supported() );
763     priv->program = g_module_open(plugin, G_MODULE_BIND_LAZY);
764     
765     if(!priv->program)
766     {
767         g_warning( "Error opening module: %s", g_module_error() );
768         return FALSE;
769     }
770     if( !g_module_symbol(priv->program, "glk_main", (gpointer *) &glk_main) )
771     {
772         g_warning( "Error finding glk_main(): %s", g_module_error() );
773         return FALSE;
774     }
775
776     extern ChimaraGlkPrivate *glk_data;
777     /* Set the thread's private data */
778     /* TODO: Do this with a GPrivate */
779     glk_data = priv;
780     
781     /* Run in a separate thread */
782         priv->thread = g_thread_create(glk_enter, glk_main, TRUE, error);
783         
784         return !(priv->thread == NULL);
785 }
786
787 /**
788  * chimara_glk_stop:
789  * @glk: a #ChimaraGlk widget
790  *
791  * Signals the Glk program running in @glk to abort. Note that if the program is
792  * caught in an infinite loop in which glk_tick() is not called, this may not
793  * work.
794  */
795 void
796 chimara_glk_stop(ChimaraGlk *glk)
797 {
798     g_return_if_fail(glk || CHIMARA_IS_GLK(glk));
799     /* TODO: check if glk is actually running a program */
800     signal_abort();
801 }
802
803 /**
804  * chimara_glk_wait:
805  * @glk: a #ChimaraGlk widget
806  *
807  * Holds up the main thread and waits for the Glk program running in @glk to 
808  * finish.
809  */
810 void
811 chimara_glk_wait(ChimaraGlk *glk)
812 {
813     g_return_if_fail(glk || CHIMARA_IS_GLK(glk));
814     
815     ChimaraGlkPrivate *priv = CHIMARA_GLK_PRIVATE(glk);
816     g_thread_join(priv->thread);
817 }