Added dynamic module loading: now the Glk program (i.e., the
[rodin/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 "window.h"
10
11 #define CHIMARA_GLK_MIN_WIDTH 0
12 #define CHIMARA_GLK_MIN_HEIGHT 0
13
14 typedef void (* glk_main_t) (void);
15
16 enum {
17     PROP_0,
18     PROP_INTERACTIVE,
19     PROP_PROTECT
20 };
21
22 enum {
23         STOPPED,
24         STARTED,
25
26         LAST_SIGNAL
27 };
28
29 static guint chimara_glk_signals[LAST_SIGNAL] = { 0 };
30
31 G_DEFINE_TYPE(ChimaraGlk, chimara_glk, GTK_TYPE_CONTAINER);
32
33 static void
34 chimara_glk_init(ChimaraGlk *self)
35 {
36     GTK_WIDGET_SET_FLAGS(GTK_WIDGET(self), GTK_NO_WINDOW);
37
38     ChimaraGlkPrivate *priv = CHIMARA_GLK_PRIVATE(self);
39     
40     priv->self = self;
41     priv->interactive = TRUE;
42     priv->protect = FALSE;
43     priv->thread = NULL;
44     priv->event_queue = NULL;
45     priv->event_lock = NULL;
46     priv->event_queue_not_empty = NULL;
47     priv->event_queue_not_full = NULL;
48     priv->abort_lock = NULL;
49     priv->abort_signalled = FALSE;
50     priv->interrupt_handler = NULL;
51     priv->root_window = NULL;
52     priv->fileref_list = NULL;
53     priv->current_stream = NULL;
54     priv->stream_list = NULL;
55 }
56
57 static void
58 chimara_glk_set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec)
59 {
60     ChimaraGlk *glk = CHIMARA_GLK(object);
61     
62     switch(prop_id) 
63     {
64         case PROP_INTERACTIVE:
65             chimara_glk_set_interactive(glk, g_value_get_boolean(value));
66             break;
67         case PROP_PROTECT:
68             chimara_glk_set_protect(glk, g_value_get_boolean(value));
69             break;
70         default:
71             G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
72     }
73 }
74
75 static void
76 chimara_glk_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
77 {
78     ChimaraGlkPrivate *priv = CHIMARA_GLK_PRIVATE(object);
79     
80     switch(prop_id)
81     {
82         case PROP_INTERACTIVE:
83             g_value_set_boolean(value, priv->interactive);
84             break;
85         case PROP_PROTECT:
86             g_value_set_boolean(value, priv->protect);
87             break;
88         default:
89             G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
90     }
91 }
92
93 static void
94 chimara_glk_finalize(GObject *object)
95 {
96     ChimaraGlk *self = CHIMARA_GLK(object);
97     ChimaraGlkPrivate *priv = CHIMARA_GLK_PRIVATE(self);
98     
99     /* Free the event queue */
100     g_mutex_lock(priv->event_lock);
101         g_queue_foreach(priv->event_queue, (GFunc)g_free, NULL);
102         g_queue_free(priv->event_queue);
103         g_cond_free(priv->event_queue_not_empty);
104         g_cond_free(priv->event_queue_not_full);
105         priv->event_queue = NULL;
106         g_mutex_unlock(priv->event_lock);
107         g_mutex_free(priv->event_lock);
108         
109         /* Free the abort signalling mechanism */
110         g_mutex_lock(priv->abort_lock);
111         /* Make sure no other thread is busy with this */
112         g_mutex_unlock(priv->abort_lock);
113         g_mutex_free(priv->abort_lock);
114         priv->abort_lock = NULL;
115     
116     G_OBJECT_CLASS(chimara_glk_parent_class)->finalize(object);
117 }
118
119 static void
120 chimara_glk_size_request(GtkWidget *widget, GtkRequisition *requisition)
121 {
122     g_return_if_fail(widget);
123     g_return_if_fail(requisition);
124     g_return_if_fail(CHIMARA_IS_GLK(widget));
125     
126     ChimaraGlkPrivate *priv = CHIMARA_GLK_PRIVATE(widget);
127     
128     /* For now, just pass the size request on to the root Glk window */
129     if(priv->root_window) { 
130         GtkWidget *child = ((winid_t)(priv->root_window->data))->frame;
131         if(GTK_WIDGET_VISIBLE(child))
132             gtk_widget_size_request(child, requisition);
133     } else {
134         requisition->width = CHIMARA_GLK_MIN_WIDTH;
135         requisition->height = CHIMARA_GLK_MIN_HEIGHT;
136     }
137 }
138
139 static void
140 chimara_glk_size_allocate(GtkWidget *widget, GtkAllocation *allocation)
141 {
142     g_return_if_fail(widget);
143     g_return_if_fail(allocation);
144     g_return_if_fail(CHIMARA_IS_GLK(widget));
145     
146     ChimaraGlkPrivate *priv = CHIMARA_GLK_PRIVATE(widget);
147     
148     widget->allocation = *allocation;
149             
150     if(priv->root_window) {
151         GtkWidget *child = ((winid_t)(priv->root_window->data))->frame;
152         if(GTK_WIDGET_VISIBLE(child))
153             gtk_widget_size_allocate(child, allocation);
154     }
155 }
156
157 static void
158 chimara_glk_forall(GtkContainer *container, gboolean include_internals,
159     GtkCallback callback, gpointer callback_data)
160 {
161     g_return_if_fail(container);
162     g_return_if_fail(CHIMARA_IS_GLK(container));
163     
164     ChimaraGlkPrivate *priv = CHIMARA_GLK_PRIVATE(container);
165     
166     if(priv->root_window) {
167         GtkWidget *child = ((winid_t)(priv->root_window->data))->frame;
168         (*callback)(child, callback_data);
169     }
170 }
171
172 static void
173 chimara_glk_stopped(ChimaraGlk *self)
174 {
175         /* TODO: Add default signal handler implementation here */
176 }
177
178 static void
179 chimara_glk_started(ChimaraGlk *self)
180 {
181         /* TODO: Add default signal handler implementation here */
182 }
183
184 static void
185 chimara_glk_class_init(ChimaraGlkClass *klass)
186 {
187     /* Override methods of parent classes */
188     GObjectClass *object_class = G_OBJECT_CLASS(klass);
189     object_class->set_property = chimara_glk_set_property;
190     object_class->get_property = chimara_glk_get_property;
191     object_class->finalize = chimara_glk_finalize;
192     
193     GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass);
194     widget_class->size_request = chimara_glk_size_request;
195     widget_class->size_allocate = chimara_glk_size_allocate;
196
197     GtkContainerClass *container_class = GTK_CONTAINER_CLASS(klass);
198     container_class->forall = chimara_glk_forall;
199
200     /* Signals */
201     klass->stopped = chimara_glk_stopped;
202     klass->started = chimara_glk_started;
203     chimara_glk_signals[STOPPED] = g_signal_new("stopped", 
204         G_OBJECT_CLASS_TYPE(klass), 0, 
205         G_STRUCT_OFFSET(ChimaraGlkClass, stopped), NULL, NULL,
206                 g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0);
207         chimara_glk_signals[STARTED] = g_signal_new ("started",
208                 G_OBJECT_CLASS_TYPE (klass), 0,
209                 G_STRUCT_OFFSET(ChimaraGlkClass, started), NULL, NULL,
210                 g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0);
211
212     /* Properties */
213     GParamSpec *pspec;
214     pspec = g_param_spec_boolean("interactive", _("Interactive"),
215         _("Whether user input is expected in the Glk program"),
216         TRUE,
217         G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_LAX_VALIDATION |
218         G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB);
219     g_object_class_install_property(object_class, PROP_INTERACTIVE, pspec);
220     pspec = g_param_spec_boolean("protect", _("Protected"),
221         _("Whether the Glk program is barred from doing file operations"),
222         FALSE,
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_PROTECT, pspec);
226     
227     /* Private data */
228     g_type_class_add_private(klass, sizeof(ChimaraGlkPrivate));
229 }
230
231 /* PUBLIC FUNCTIONS */
232
233 GtkWidget *
234 chimara_glk_new(void)
235 {
236     ChimaraGlk *self = CHIMARA_GLK(g_object_new(CHIMARA_TYPE_GLK, NULL));
237     ChimaraGlkPrivate *priv = CHIMARA_GLK_PRIVATE(self);
238     
239     priv->event_queue = g_queue_new();
240     priv->event_lock = g_mutex_new();
241     priv->event_queue_not_empty = g_cond_new();
242     priv->event_queue_not_full = g_cond_new();
243     priv->abort_lock = g_mutex_new();
244     
245     return GTK_WIDGET(self);
246 }
247
248 void 
249 chimara_glk_set_interactive(ChimaraGlk *glk, gboolean interactive)
250 {
251     g_return_if_fail(glk || CHIMARA_IS_GLK(glk));
252     
253     ChimaraGlkPrivate *priv = CHIMARA_GLK_PRIVATE(glk);
254     priv->interactive = interactive;
255 }
256
257 gboolean 
258 chimara_glk_get_interactive(ChimaraGlk *glk)
259 {
260     g_return_val_if_fail(glk || CHIMARA_IS_GLK(glk), FALSE);
261     
262     ChimaraGlkPrivate *priv = CHIMARA_GLK_PRIVATE(glk);
263     return priv->interactive;
264 }
265
266 void 
267 chimara_glk_set_protect(ChimaraGlk *glk, gboolean protect)
268 {
269     g_return_if_fail(glk || CHIMARA_IS_GLK(glk));
270     
271     ChimaraGlkPrivate *priv = CHIMARA_GLK_PRIVATE(glk);
272     priv->protect = protect;
273 }
274
275 gboolean 
276 chimara_glk_get_protect(ChimaraGlk *glk)
277 {
278     g_return_val_if_fail(glk || CHIMARA_IS_GLK(glk), FALSE);
279     
280     ChimaraGlkPrivate *priv = CHIMARA_GLK_PRIVATE(glk);
281     return priv->protect;
282 }
283
284 /* glk_enter() is the actual function called in the new thread in which glk_main() runs.  */
285 static gpointer
286 glk_enter(gpointer glk_main)
287 {
288         ((glk_main_t)glk_main)();
289         return NULL;
290 }
291
292 gboolean
293 chimara_glk_run(ChimaraGlk *glk, gchar *plugin, GError **error)
294 {
295     g_return_val_if_fail(glk || CHIMARA_IS_GLK(glk), FALSE);
296     g_return_val_if_fail(plugin, FALSE);
297     
298     /* Open the module to run */
299     GModule *module;
300     glk_main_t glk_main;
301     g_assert( g_module_supported() );
302     module = g_module_open(plugin, G_MODULE_BIND_LAZY);
303     
304     if(!module)
305     {
306         g_warning( "Error opening module: %s", g_module_error() );
307         return FALSE;
308     }
309     if( !g_module_symbol(module, "glk_main", (gpointer *) &glk_main) )
310     {
311         g_warning( "Error finding glk_main(): %s", g_module_error() );
312         return FALSE;
313     }
314     
315     ChimaraGlkPrivate *priv = CHIMARA_GLK_PRIVATE(glk);
316     extern ChimaraGlkPrivate *glk_data;
317     /* Set the thread's private data */
318     /* TODO: Do this with a GPrivate */
319     glk_data = priv;
320     
321     /* Run in a separate thread */
322         priv->thread = g_thread_create(glk_enter, glk_main, TRUE, error);
323         
324         /* Close module */
325 /*      if( !g_module_close(module) )
326         {
327             g_warning( "Error closing module: %s", g_module_error() );
328             return FALSE;
329         }*/
330         
331         return !(priv->thread == NULL);
332 }
333
334 void
335 chimara_glk_stop(ChimaraGlk *glk)
336 {
337     g_return_if_fail(glk || CHIMARA_IS_GLK(glk));
338     
339     signal_abort();
340 }
341
342 void
343 chimara_glk_wait(ChimaraGlk *glk)
344 {
345     ChimaraGlkPrivate *priv = CHIMARA_GLK_PRIVATE(glk);
346     g_thread_join(priv->thread);
347 }