Implemented "started" and "stopped" signals
[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 GtkWidget *
240 chimara_glk_new(void)
241 {
242     ChimaraGlk *self = CHIMARA_GLK(g_object_new(CHIMARA_TYPE_GLK, NULL));
243     ChimaraGlkPrivate *priv = CHIMARA_GLK_PRIVATE(self);
244     
245     priv->event_queue = g_queue_new();
246     priv->event_lock = g_mutex_new();
247     priv->event_queue_not_empty = g_cond_new();
248     priv->event_queue_not_full = g_cond_new();
249     priv->abort_lock = g_mutex_new();
250     
251     return GTK_WIDGET(self);
252 }
253
254 void 
255 chimara_glk_set_interactive(ChimaraGlk *glk, gboolean interactive)
256 {
257     g_return_if_fail(glk || CHIMARA_IS_GLK(glk));
258     
259     ChimaraGlkPrivate *priv = CHIMARA_GLK_PRIVATE(glk);
260     priv->interactive = interactive;
261 }
262
263 gboolean 
264 chimara_glk_get_interactive(ChimaraGlk *glk)
265 {
266     g_return_val_if_fail(glk || CHIMARA_IS_GLK(glk), FALSE);
267     
268     ChimaraGlkPrivate *priv = CHIMARA_GLK_PRIVATE(glk);
269     return priv->interactive;
270 }
271
272 void 
273 chimara_glk_set_protect(ChimaraGlk *glk, gboolean protect)
274 {
275     g_return_if_fail(glk || CHIMARA_IS_GLK(glk));
276     
277     ChimaraGlkPrivate *priv = CHIMARA_GLK_PRIVATE(glk);
278     priv->protect = protect;
279 }
280
281 gboolean 
282 chimara_glk_get_protect(ChimaraGlk *glk)
283 {
284     g_return_val_if_fail(glk || CHIMARA_IS_GLK(glk), FALSE);
285     
286     ChimaraGlkPrivate *priv = CHIMARA_GLK_PRIVATE(glk);
287     return priv->protect;
288 }
289
290 /* glk_enter() is the actual function called in the new thread in which glk_main() runs.  */
291 static gpointer
292 glk_enter(gpointer glk_main)
293 {
294     extern ChimaraGlkPrivate *glk_data;
295     g_signal_emit_by_name(glk_data->self, "started");
296         ((glk_main_t)glk_main)();
297         g_signal_emit_by_name(glk_data->self, "stopped");
298         return NULL;
299 }
300
301 gboolean
302 chimara_glk_run(ChimaraGlk *glk, gchar *plugin, GError **error)
303 {
304     g_return_val_if_fail(glk || CHIMARA_IS_GLK(glk), FALSE);
305     g_return_val_if_fail(plugin, FALSE);
306     
307     ChimaraGlkPrivate *priv = CHIMARA_GLK_PRIVATE(glk);
308     
309     /* Open the module to run */
310     glk_main_t glk_main;
311     g_assert( g_module_supported() );
312     priv->program = g_module_open(plugin, G_MODULE_BIND_LAZY);
313     
314     if(!priv->program)
315     {
316         g_warning( "Error opening module: %s", g_module_error() );
317         return FALSE;
318     }
319     if( !g_module_symbol(priv->program, "glk_main", (gpointer *) &glk_main) )
320     {
321         g_warning( "Error finding glk_main(): %s", g_module_error() );
322         return FALSE;
323     }
324
325     extern ChimaraGlkPrivate *glk_data;
326     /* Set the thread's private data */
327     /* TODO: Do this with a GPrivate */
328     glk_data = priv;
329     
330     /* Run in a separate thread */
331         priv->thread = g_thread_create(glk_enter, glk_main, TRUE, error);
332         
333         return !(priv->thread == NULL);
334 }
335
336 void
337 chimara_glk_stop(ChimaraGlk *glk)
338 {
339     g_return_if_fail(glk || CHIMARA_IS_GLK(glk));
340     
341     signal_abort();
342 }
343
344 void
345 chimara_glk_wait(ChimaraGlk *glk)
346 {
347     ChimaraGlkPrivate *priv = CHIMARA_GLK_PRIVATE(glk);
348     g_thread_join(priv->thread);
349 }