Don't use getline(), it is not supported under BSD/OSX
[rodin/chimara.git] / libchimara / chimara-if.c
1 #include <errno.h>
2 #include <stdlib.h>
3 #include <glib.h>
4 #include <glib-object.h>
5 #include <config.h>
6 #include <glib/gi18n-lib.h>
7 #include "chimara-if.h"
8 #include "chimara-glk.h"
9 #include "chimara-marshallers.h"
10 #include "init.h"
11
12 static gboolean supported_formats[CHIMARA_IF_NUM_FORMATS][CHIMARA_IF_NUM_INTERPRETERS] = {
13         /* Frotz Nitfol Glulxe Git */
14         { TRUE,  TRUE,  FALSE, FALSE }, /* Z5 */
15         { TRUE,  TRUE,  FALSE, FALSE }, /* Z6 */
16         { TRUE,  TRUE,  FALSE, FALSE }, /* Z8 */
17         { TRUE,  TRUE,  FALSE, FALSE }, /* Zblorb */
18         { FALSE, FALSE, TRUE,  TRUE  }, /* Glulx */
19         { FALSE, FALSE, TRUE,  TRUE  }  /* Gblorb */
20 };
21 static gchar *format_names[CHIMARA_IF_NUM_FORMATS] = {
22         N_("Z-code version 5"),
23         N_("Z-code version 6"),
24         N_("Z-code version 8"),
25         N_("Blorbed Z-code"),
26         N_("Glulx"),
27         N_("Blorbed Glulx")
28 };
29 static gchar *interpreter_names[CHIMARA_IF_NUM_INTERPRETERS] = {
30         N_("Frotz"), N_("Nitfol"), N_("Glulxe"), N_("Git")
31 };
32 static gchar *plugin_names[CHIMARA_IF_NUM_INTERPRETERS] = {
33         "frotz", "nitfol", "glulxe", "git"
34 };
35
36 typedef struct _ChimaraIFPrivate {
37         ChimaraIFInterpreter preferred_interpreter[CHIMARA_IF_NUM_FORMATS];
38 } ChimaraIFPrivate;
39
40 #define CHIMARA_IF_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE((o), CHIMARA_TYPE_IF, ChimaraIFPrivate))
41 #define CHIMARA_IF_USE_PRIVATE(o, n) ChimaraIFPrivate *n = CHIMARA_IF_PRIVATE(o)
42
43 enum {
44         PROP_0
45 };
46
47 enum {
48         COMMAND,
49
50         LAST_SIGNAL
51 };
52
53 static guint chimara_if_signals[LAST_SIGNAL] = { 0 };
54
55 G_DEFINE_TYPE(ChimaraIF, chimara_if, CHIMARA_TYPE_GLK);
56
57 static void
58 chimara_if_init(ChimaraIF *self)
59 {
60         CHIMARA_IF_USE_PRIVATE(self, priv);
61         priv->preferred_interpreter[CHIMARA_IF_FORMAT_Z5] = CHIMARA_IF_INTERPRETER_FROTZ;
62         priv->preferred_interpreter[CHIMARA_IF_FORMAT_Z6] = CHIMARA_IF_INTERPRETER_FROTZ;
63         priv->preferred_interpreter[CHIMARA_IF_FORMAT_Z8] = CHIMARA_IF_INTERPRETER_FROTZ;
64         priv->preferred_interpreter[CHIMARA_IF_FORMAT_Z_BLORB] = CHIMARA_IF_INTERPRETER_FROTZ;
65         priv->preferred_interpreter[CHIMARA_IF_FORMAT_GLULX] = CHIMARA_IF_INTERPRETER_GLULXE;
66         priv->preferred_interpreter[CHIMARA_IF_FORMAT_GLULX_BLORB] = CHIMARA_IF_INTERPRETER_GLULXE;
67 }
68
69 static void
70 chimara_if_set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec)
71 {
72     switch(prop_id)
73     {
74         default:
75             G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
76     }
77 }
78
79 static void
80 chimara_if_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
81 {
82     switch(prop_id)
83     {
84
85         default:
86             G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
87     }
88 }
89
90 static void
91 chimara_if_finalize(GObject *object)
92 {
93     G_OBJECT_CLASS(chimara_if_parent_class)->finalize(object);
94 }
95
96 static void
97 chimara_if_command(ChimaraIF *self, gchar *input, gchar *response)
98 {
99         /* Default signal handler */
100 }
101
102 static void
103 chimara_if_class_init(ChimaraIFClass *klass)
104 {
105         /* Override methods of parent classes */
106         GObjectClass *object_class = G_OBJECT_CLASS(klass);
107         object_class->set_property = chimara_if_set_property;
108         object_class->get_property = chimara_if_get_property;
109         object_class->finalize = chimara_if_finalize;
110
111         /* Signals */
112         klass->command = chimara_if_command;
113         /* Gtk-Doc for command */
114         chimara_if_signals[COMMAND] = g_signal_new("command",
115                 G_OBJECT_CLASS_TYPE(klass), 0,
116                 G_STRUCT_OFFSET(ChimaraIFClass, command), NULL, NULL,
117                 chimara_marshal_VOID__STRING_STRING,
118                 G_TYPE_NONE, 2, G_TYPE_STRING, G_TYPE_STRING);
119
120         /* Properties */
121         /* Gtk-Doc for property */
122         /* g_object_class_install_property(object_class, PROPERTY, ...); */
123
124         /* Private data */
125         g_type_class_add_private(klass, sizeof(ChimaraIFPrivate));
126 }
127
128 /* PUBLIC FUNCTIONS */
129
130 /**
131  * chimara_if_new:
132  *
133  * Creates and initializes a new #ChimaraIF widget.
134  *
135  * Return value: a #ChimaraIF widget, with a floating reference.
136  */
137 GtkWidget *
138 chimara_if_new(void)
139 {
140         /* This is a library entry point; initialize the library */
141         chimara_init();
142     return GTK_WIDGET(g_object_new(CHIMARA_TYPE_IF, NULL));
143 }
144
145 void
146 chimara_if_set_preferred_interpreter(ChimaraIF *self, ChimaraIFFormat format, ChimaraIFInterpreter interpreter)
147 {
148         g_return_if_fail(self && CHIMARA_IS_IF(self));
149         g_return_if_fail(format < CHIMARA_IF_NUM_FORMATS);
150         g_return_if_fail(format < CHIMARA_IF_NUM_INTERPRETERS);
151
152         CHIMARA_IF_USE_PRIVATE(self, priv);
153
154         if(supported_formats[format][interpreter])
155                 priv->preferred_interpreter[format] = interpreter;
156         else
157                 g_warning("Format '%s' is not supported by interpreter '%s'", format_names[format], interpreter_names[interpreter]);
158 }
159
160 ChimaraIFInterpreter
161 chimara_if_get_preferred_interpreter(ChimaraIF *self, ChimaraIFFormat format)
162 {
163         g_return_val_if_fail(self && CHIMARA_IS_IF(self), -1);
164         g_return_val_if_fail(format < CHIMARA_IF_NUM_FORMATS, -1);
165         CHIMARA_IF_USE_PRIVATE(self, priv);
166         return priv->preferred_interpreter[format];
167 }
168
169 /* Opens a '.la' file and finds the name of the plugin library. g_free() the
170  * string when done. */
171 static gchar *
172 find_dlname(const gchar *pluginfile, GError **error)
173 {
174         /* Find the name of the shared library */
175         gchar *dlname;
176         FILE *plugin = fopen(pluginfile, "r");
177         if(!plugin)
178         {
179                 g_set_error(error, G_FILE_ERROR, errno, "Error opening '%s': %s", pluginfile, g_strerror(errno));
180                 return NULL;
181         }
182         gchar line[256];
183         while( fgets(line, 256, plugin) != NULL)
184         {       
185                 if(g_str_has_prefix(line, "dlname='"))
186                 {
187                         dlname = g_strndup(line + 8, strlen(line) - 10);
188                         break;
189                 }
190         }
191         free(line);
192         if(!dlname)
193         {
194                 g_set_error(error, G_FILE_ERROR, errno, "Error reading '%s': %s", pluginfile, g_strerror(errno));
195                 return NULL;
196         }
197         if(fclose(plugin))
198         {
199                 g_set_error(error, G_FILE_ERROR, errno, "Error closing '%s': %s", pluginfile, g_strerror(errno));
200                 return NULL;
201         }
202         return dlname;
203 }
204
205 gboolean 
206 chimara_if_run_game(ChimaraIF *self, gchar *gamefile, GError **error)
207 {
208         g_return_val_if_fail(self && CHIMARA_IS_IF(self), FALSE);
209         g_return_val_if_fail(gamefile, FALSE);
210         
211         CHIMARA_IF_USE_PRIVATE(self, priv);
212
213         /* Find out what format the game is */
214         /* TODO: Look inside the file instead of just looking at the extension */
215         ChimaraIFFormat format = CHIMARA_IF_FORMAT_Z5;
216         if(g_str_has_suffix(gamefile, ".z5"))
217                 format = CHIMARA_IF_FORMAT_Z5;
218         else if(g_str_has_suffix(gamefile, ".z6"))
219                 format = CHIMARA_IF_FORMAT_Z6;
220         else if(g_str_has_suffix(gamefile, ".z8"))
221                 format = CHIMARA_IF_FORMAT_Z8;
222         else if(g_str_has_suffix(gamefile, ".zlb") || g_str_has_suffix(gamefile, ".zblorb"))
223                 format = CHIMARA_IF_FORMAT_Z_BLORB;
224         else if(g_str_has_suffix(gamefile, ".ulx"))
225                 format = CHIMARA_IF_FORMAT_GLULX;
226         else if(g_str_has_suffix(gamefile, ".blb") || g_str_has_suffix(gamefile, ".blorb") || g_str_has_suffix(gamefile, ".glb") || g_str_has_suffix(gamefile, ".gblorb"))
227                 format = CHIMARA_IF_FORMAT_GLULX_BLORB;
228         
229         /* Now decide what interpreter to use */
230         ChimaraIFInterpreter interpreter = priv->preferred_interpreter[format];
231         gchar *libtoolfile = g_strconcat(plugin_names[interpreter], ".la", NULL);
232 #ifndef RELEASE
233         /* If there is a plugin in the source tree, use that */
234         gboolean use_installed = FALSE;
235         gchar *libtoolpath = g_build_filename("..", "interpreters", plugin_names[interpreter], libtoolfile, NULL);
236         if( !g_file_test(libtoolpath, G_FILE_TEST_EXISTS) ) 
237         {
238                 g_free(libtoolpath);
239 #endif
240                 gchar *libtoolpath = g_build_filename(PLUGINDIR, libtoolfile, NULL);
241                 if( !g_file_test(libtoolpath, G_FILE_TEST_EXISTS) ) 
242                 {
243                         g_free(libtoolpath);
244                         g_free(libtoolfile);
245                         g_warning("Cannot open %s plugin file", interpreter_names[interpreter]);
246                         /* TODO: set error */
247                         return FALSE;
248                 }
249 #ifndef RELEASE
250                 use_installed = TRUE;
251         }
252 #endif
253         g_free(libtoolfile);
254         
255         gchar *dlname = find_dlname(libtoolpath, error);
256         g_free(libtoolpath);
257         if(!dlname)
258                 return FALSE;
259         gchar *pluginpath;
260 #ifndef RELEASE
261         if(use_installed)
262 #endif
263                 pluginpath = g_build_filename(PLUGINDIR, dlname, NULL);
264 #ifndef RELEASE
265         else
266                 pluginpath = g_build_filename("..", "interpreters", plugin_names[interpreter], LT_OBJDIR, dlname, NULL);
267 #endif
268         char *argv[3] = { pluginpath, gamefile, NULL };
269         gboolean retval = chimara_glk_run(CHIMARA_GLK(self), pluginpath, 2, argv, error);
270         g_free(dlname);
271         return retval;
272 }