5a62517a584a0378e278e2407030b808ca98e4e4
[projects/chimara/chimara.git] / src / chimara-glk.c
1 /* licensing and copyright information here */
2
3 #include <gtk/gtk.h>
4 #include <glib/gi18n.h>
5 #include <gmodule.h>
6 #include "chimara-glk.h"
7 #include "chimara-glk-private.h"
8 #include "glk.h"
9 #include "abort.h"
10 #include "window.h"
11
12 #define CHIMARA_GLK_MIN_WIDTH 0
13 #define CHIMARA_GLK_MIN_HEIGHT 0
14
15 typedef void (* glk_main_t) (void);
16
17 enum {
18     PROP_0,
19     PROP_INTERACTIVE,
20     PROP_PROTECT
21 };
22
23 enum {
24         STOPPED,
25         STARTED,
26
27         LAST_SIGNAL
28 };
29
30 static guint chimara_glk_signals[LAST_SIGNAL] = { 0 };
31
32 G_DEFINE_TYPE(ChimaraGlk, chimara_glk, GTK_TYPE_CONTAINER);
33
34 static void
35 chimara_glk_init(ChimaraGlk *self)
36 {
37     GTK_WIDGET_SET_FLAGS(GTK_WIDGET(self), GTK_NO_WINDOW);
38
39     ChimaraGlkPrivate *priv = CHIMARA_GLK_PRIVATE(self);
40     
41     priv->self = self;
42     priv->interactive = TRUE;
43     priv->protect = FALSE;
44     priv->program = NULL;
45     priv->thread = NULL;
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;
57 }
58
59 static void
60 chimara_glk_set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec)
61 {
62     ChimaraGlk *glk = CHIMARA_GLK(object);
63     
64     switch(prop_id) 
65     {
66         case PROP_INTERACTIVE:
67             chimara_glk_set_interactive(glk, g_value_get_boolean(value));
68             break;
69         case PROP_PROTECT:
70             chimara_glk_set_protect(glk, g_value_get_boolean(value));
71             break;
72         default:
73             G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
74     }
75 }
76
77 static void
78 chimara_glk_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
79 {
80     ChimaraGlkPrivate *priv = CHIMARA_GLK_PRIVATE(object);
81     
82     switch(prop_id)
83     {
84         case PROP_INTERACTIVE:
85             g_value_set_boolean(value, priv->interactive);
86             break;
87         case PROP_PROTECT:
88             g_value_set_boolean(value, priv->protect);
89             break;
90         default:
91             G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
92     }
93 }
94
95 static void
96 chimara_glk_finalize(GObject *object)
97 {
98     ChimaraGlk *self = CHIMARA_GLK(object);
99     ChimaraGlkPrivate *priv = CHIMARA_GLK_PRIVATE(self);
100     
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);
110         
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;
117
118     G_OBJECT_CLASS(chimara_glk_parent_class)->finalize(object);
119 }
120
121 static void
122 chimara_glk_size_request(GtkWidget *widget, GtkRequisition *requisition)
123 {
124     g_return_if_fail(widget);
125     g_return_if_fail(requisition);
126     g_return_if_fail(CHIMARA_IS_GLK(widget));
127     
128     ChimaraGlkPrivate *priv = CHIMARA_GLK_PRIVATE(widget);
129     
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);
135     } else {
136         requisition->width = CHIMARA_GLK_MIN_WIDTH;
137         requisition->height = CHIMARA_GLK_MIN_HEIGHT;
138     }
139 }
140
141 static void
142 chimara_glk_size_allocate(GtkWidget *widget, GtkAllocation *allocation)
143 {
144     g_return_if_fail(widget);
145     g_return_if_fail(allocation);
146     g_return_if_fail(CHIMARA_IS_GLK(widget));
147     
148     ChimaraGlkPrivate *priv = CHIMARA_GLK_PRIVATE(widget);
149     
150     widget->allocation = *allocation;
151             
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);
156     }
157 }
158
159 static void
160 chimara_glk_forall(GtkContainer *container, gboolean include_internals,
161     GtkCallback callback, gpointer callback_data)
162 {
163     g_return_if_fail(container);
164     g_return_if_fail(CHIMARA_IS_GLK(container));
165     
166     ChimaraGlkPrivate *priv = CHIMARA_GLK_PRIVATE(container);
167     
168     if(priv->root_window) {
169         GtkWidget *child = ((winid_t)(priv->root_window->data))->frame;
170         (*callback)(child, callback_data);
171     }
172 }
173
174 static void
175 chimara_glk_stopped(ChimaraGlk *self)
176 {
177     ChimaraGlkPrivate *priv = CHIMARA_GLK_PRIVATE(self);
178
179     /* Free the plugin */
180         if( priv->program && !g_module_close(priv->program) )
181             g_warning( "Error closing module: %s", g_module_error() );
182 }
183
184 static void
185 chimara_glk_started(ChimaraGlk *self)
186 {
187         /* TODO: Add default signal handler implementation here */
188 }
189
190 static void
191 chimara_glk_class_init(ChimaraGlkClass *klass)
192 {
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;
198     
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;
202
203     GtkContainerClass *container_class = GTK_CONTAINER_CLASS(klass);
204     container_class->forall = chimara_glk_forall;
205
206     /* Signals */
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);
217
218     /* Properties */
219     GParamSpec *pspec;
220     pspec = g_param_spec_boolean("interactive", _("Interactive"),
221         _("Whether user input is expected in the Glk program"),
222         TRUE,
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"),
228         FALSE,
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);
232     
233     /* Private data */
234     g_type_class_add_private(klass, sizeof(ChimaraGlkPrivate));
235 }
236
237 /* PUBLIC FUNCTIONS */
238
239 /**
240  * chimara_glk_new:
241  *
242  * Creates and initializes a new #ChimaraGlk widget.
243  *
244  * Return value: a #ChimaraGlk widget, with a floating reference.
245  */
246 GtkWidget *
247 chimara_glk_new(void)
248 {
249     ChimaraGlk *self = CHIMARA_GLK(g_object_new(CHIMARA_TYPE_GLK, NULL));
250     ChimaraGlkPrivate *priv = CHIMARA_GLK_PRIVATE(self);
251     
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();
257     
258     return GTK_WIDGET(self);
259 }
260
261 /**
262  * chimara_glk_set_interactive:
263  * @glk: a #ChimaraGlk widget
264  * @interactive: whether the widget should expect user input
265  *
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.
272  */
273 void 
274 chimara_glk_set_interactive(ChimaraGlk *glk, gboolean interactive)
275 {
276     g_return_if_fail(glk || CHIMARA_IS_GLK(glk));
277     
278     ChimaraGlkPrivate *priv = CHIMARA_GLK_PRIVATE(glk);
279     priv->interactive = interactive;
280 }
281
282 /**
283  * chimara_glk_get_interactive:
284  * @glk: a #ChimaraGlk widget
285  *
286  * Returns whether @glk is interactive (expecting user input). See 
287  * chimara_glk_set_interactive().
288  *
289  * Return value: %TRUE if @glk is interactive.
290  */
291 gboolean 
292 chimara_glk_get_interactive(ChimaraGlk *glk)
293 {
294     g_return_val_if_fail(glk || CHIMARA_IS_GLK(glk), FALSE);
295     
296     ChimaraGlkPrivate *priv = CHIMARA_GLK_PRIVATE(glk);
297     return priv->interactive;
298 }
299
300 /**
301  * chimara_glk_set_protect:
302  * @glk: a #ChimaraGlk widget
303  * @protect: whether the widget should allow the Glk program to do file 
304  * operations
305  *
306  * Sets the #ChimaraGlk:protect property of @glk. In protect mode, the Glk 
307  * program is not allowed to do file operations.
308  */
309 void 
310 chimara_glk_set_protect(ChimaraGlk *glk, gboolean protect)
311 {
312     g_return_if_fail(glk || CHIMARA_IS_GLK(glk));
313     
314     ChimaraGlkPrivate *priv = CHIMARA_GLK_PRIVATE(glk);
315     priv->protect = protect;
316 }
317
318 /**
319  * chimara_glk_get_protect:
320  * @glk: a #ChimaraGlk widget
321  *
322  * Returns whether @glk is in protect mode (banned from doing file operations).
323  * See chimara_glk_set_protect().
324  *
325  * Return value: %TRUE if @glk is in protect mode.
326  */
327 gboolean 
328 chimara_glk_get_protect(ChimaraGlk *glk)
329 {
330     g_return_val_if_fail(glk || CHIMARA_IS_GLK(glk), FALSE);
331     
332     ChimaraGlkPrivate *priv = CHIMARA_GLK_PRIVATE(glk);
333     return priv->protect;
334 }
335
336 /* glk_enter() is the actual function called in the new thread in which glk_main() runs.  */
337 static gpointer
338 glk_enter(gpointer glk_main)
339 {
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");
344         return NULL;
345 }
346
347 /**
348  * chimara_glk_run:
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
352  *
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.
355  *
356  * Return value: %TRUE if the Glk program was started successfully.
357  */
358 gboolean
359 chimara_glk_run(ChimaraGlk *glk, gchar *plugin, GError **error)
360 {
361     g_return_val_if_fail(glk || CHIMARA_IS_GLK(glk), FALSE);
362     g_return_val_if_fail(plugin, FALSE);
363     
364     ChimaraGlkPrivate *priv = CHIMARA_GLK_PRIVATE(glk);
365     
366     /* Open the module to run */
367     glk_main_t glk_main;
368     g_assert( g_module_supported() );
369     priv->program = g_module_open(plugin, G_MODULE_BIND_LAZY);
370     
371     if(!priv->program)
372     {
373         g_warning( "Error opening module: %s", g_module_error() );
374         return FALSE;
375     }
376     if( !g_module_symbol(priv->program, "glk_main", (gpointer *) &glk_main) )
377     {
378         g_warning( "Error finding glk_main(): %s", g_module_error() );
379         return FALSE;
380     }
381
382     extern ChimaraGlkPrivate *glk_data;
383     /* Set the thread's private data */
384     /* TODO: Do this with a GPrivate */
385     glk_data = priv;
386     
387     /* Run in a separate thread */
388         priv->thread = g_thread_create(glk_enter, glk_main, TRUE, error);
389         
390         return !(priv->thread == NULL);
391 }
392
393 /**
394  * chimara_glk_stop:
395  * @glk: a #ChimaraGlk widget
396  *
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
399  * work.
400  */
401 void
402 chimara_glk_stop(ChimaraGlk *glk)
403 {
404     g_return_if_fail(glk || CHIMARA_IS_GLK(glk));
405     /* TODO: check if glk is actually running a program */
406     signal_abort();
407 }
408
409 /**
410  * chimara_glk_wait:
411  * @glk: a #ChimaraGlk widget
412  *
413  * Holds up the main thread and waits for the Glk program running in @glk to 
414  * finish.
415  */
416 void
417 chimara_glk_wait(ChimaraGlk *glk)
418 {
419     g_return_if_fail(glk || CHIMARA_IS_GLK(glk));
420     
421     ChimaraGlkPrivate *priv = CHIMARA_GLK_PRIVATE(glk);
422     g_thread_join(priv->thread);
423 }