Implemented Unix startup code, argument parsing, and platform-dependent functions
authorfliep <fliep@ddfedd41-794f-dd11-ae45-00112f111e67>
Sun, 24 May 2009 18:11:03 +0000 (18:11 +0000)
committerfliep <fliep@ddfedd41-794f-dd11-ae45-00112f111e67>
Sun, 24 May 2009 18:11:03 +0000 (18:11 +0000)
libchimara/Makefile.am
libchimara/chimara-glk-private.h
libchimara/chimara-glk.c
libchimara/fileref.c
libchimara/glkunix.c [new file with mode: 0644]
libchimara/glkunix.h [new file with mode: 0644]
libchimara/stream.c
libchimara/stream.h

index 87f0498a561992a6eba3a9031a2004e371b19459..fa187478a267cb4d17d78a77be5c06a9b3f7c632 100644 (file)
@@ -13,6 +13,7 @@ libchimara_la_SOURCES = \
        fileref.c fileref.h \
        gestalt.c \
        glk.c glk.h \
+       glkunix.c glkunix.h \
        magic.c magic.h \
        mouse.c \
        input.c input.h \
@@ -32,4 +33,4 @@ libchimara_la_LDFLAGS = -no-undefined -export-symbols-regex "^(glk_|chimara_glk_
 libchimara_includedir = $(includedir)/chimara/chimara
 libchimara_include_HEADERS = chimara-glk.h
 
-EXTRA_DIST = doc.c
+EXTRA_DIST = doc.c glkstart.c
index 2cfc523bb08691c73749f1d9e9b1819ef5c0b2d4..e1067b247506fd78aea1b759709182e470807175 100644 (file)
@@ -15,6 +15,8 @@ typedef struct _ChimaraGlkPrivate ChimaraGlkPrivate;
 struct _ChimaraGlkPrivate {
     /* Pointer back to the widget itself for use in thread */
     ChimaraGlk *self;
+
+       /* *** Widget properties *** */
     /* Whether user input is expected */
     gboolean interactive;
     /* Whether file operations are allowed */
@@ -25,6 +27,8 @@ struct _ChimaraGlkPrivate {
        PangoFontDescription *monospace_font_desc;
        /* Spacing between Glk windows */
        guint spacing;
+
+       /* *** Threading data *** */
     /* Glk program loaded in widget */
     GModule *program;
     /* Thread in which Glk program is run */
@@ -42,6 +46,8 @@ struct _ChimaraGlkPrivate {
        GCond *rearranged;
        gboolean needs_rearrange;
        gboolean ignore_next_arrange_event;
+
+       /* *** Glk library data *** */
     /* User-defined interrupt handler */
     void (*interrupt_handler)(void);
     /* Global tree of all windows */
@@ -58,6 +64,12 @@ struct _ChimaraGlkPrivate {
        giblorb_map_t *resource_map;
        /* File stream pointing to the blorb used as current resource map */
        strid_t resource_file;
+
+       /* *** Platform-dependent Glk library data *** */
+       /* Flag for functions to find out if they are being called from startup code */
+       gboolean in_startup;
+       /* "Current directory" for creating filerefs */
+       gchar *current_dir;
 };
 
 #define CHIMARA_GLK_PRIVATE(obj) \
index fc4c446ed05aa51ecead7b2f862061a7588b5b2c..2c61dacce675a549305625bd279052d332746a98 100644 (file)
@@ -11,6 +11,7 @@
 #include "abort.h"
 #include "window.h"
 #include "glkstart.h"
+#include "glkunix.h"
 
 #define CHIMARA_GLK_MIN_WIDTH 0
 #define CHIMARA_GLK_MIN_HEIGHT 0
@@ -41,7 +42,7 @@
  */
 
 typedef void (* glk_main_t) (void);
-typedef void (* glkunix_startup_code_t) (glkunix_startup_t*);
+typedef int (* glkunix_startup_code_t) (glkunix_startup_t*);
 
 enum {
     PROP_0,
@@ -93,6 +94,8 @@ chimara_glk_init(ChimaraGlk *self)
     priv->current_stream = NULL;
     priv->stream_list = NULL;
        priv->timer_id = 0;
+       priv->in_startup = FALSE;
+       priv->current_dir = NULL;
 }
 
 static void
@@ -182,6 +185,7 @@ chimara_glk_finalize(GObject *object)
        /* Free private data */
        pango_font_description_free(priv->default_font_desc);
        pango_font_description_free(priv->monospace_font_desc);
+       g_free(priv->current_dir);
        
     G_OBJECT_CLASS(chimara_glk_parent_class)->finalize(object);
 }
@@ -926,12 +930,19 @@ glk_enter(gpointer glk_main)
  * @glk: a #ChimaraGlk widget
  * @plugin: path to a plugin module compiled with <filename 
  * class="header">glk.h</filename>
+ * @argc: Number of command line arguments in @argv
+ * @argv: Array of command line arguments to pass to the plugin
  * @error: location to store a <link linkend="glib-GError">GError</link>, or 
  * %NULL
  *
- * Opens a Glk program compiled as a plugin and runs its glk_main() function in
+ * Opens a Glk program compiled as a plugin. Sorts out its command line
+ * arguments from #glkunix_arguments, calls its startup function
+ * glkunix_startup_code(), and then calls its main function glk_main() in
  * a separate thread. On failure, returns %FALSE and sets @error.
  *
+ * The plugin must at least export a glk_main() function; #glkunix_arguments and
+ * glkunix_startup_code() are optional.
+ *
  * Return value: %TRUE if the Glk program was started successfully.
  */
 gboolean
@@ -942,7 +953,6 @@ chimara_glk_run(ChimaraGlk *glk, gchar *plugin, int argc, char *argv[], GError *
     
     ChimaraGlkPrivate *priv = CHIMARA_GLK_PRIVATE(glk);
     
-
     /* Open the module to run */
     glk_main_t glk_main;
        glkunix_startup_code_t glkunix_startup_code;
@@ -960,20 +970,43 @@ chimara_glk_run(ChimaraGlk *glk, gchar *plugin, int argc, char *argv[], GError *
         return FALSE;
     }
 
-    extern ChimaraGlkPrivate *glk_data;
+       extern ChimaraGlkPrivate *glk_data;
     /* Set the thread's private data */
     /* TODO: Do this with a GPrivate */
     glk_data = priv;
-
+       
     if( g_module_symbol(priv->program, "glkunix_startup_code", (gpointer *) &glkunix_startup_code) )
     {
                glkunix_startup_t data;
-               data.argc = argc;
-               data.argv = argv;
+               glkunix_argumentlist_t *glkunix_arguments;
+               gboolean startup_succeeded;
 
-               glkunix_startup_code(&data);
-    }
+               if( !(g_module_symbol(priv->program, "glkunix_arguments", (gpointer *) &glkunix_arguments) && parse_command_line(glkunix_arguments, argc, argv, &data)) )
+               {
+                       /* arguments could not be parsed, so create data ourselves */
+                       data.argc = 1;
+                       data.argv = g_new0(gchar *, 1);
+               }
 
+               /* Set the program name */
+               data.argv[0] = g_strdup(plugin);
+               
+               priv->in_startup = TRUE;
+               startup_succeeded = glkunix_startup_code(&data);
+               priv->in_startup = FALSE;
+               
+               gchar **ptr = data.argv;
+               while(*ptr++)
+                       g_free(*ptr);
+               g_free(data.argv);
+               
+               if(!startup_succeeded)
+               {
+                       chimara_glk_stopped(glk);
+                       return FALSE;
+               }
+    }
+       
     /* Run in a separate thread */
        priv->thread = g_thread_create(glk_enter, glk_main, TRUE, error);
        
index 08ae5843d2318156619ee1ffdf92d17342ae2370..516af027f6bf44157fd41b985b80458b53722fb3 100644 (file)
@@ -137,7 +137,8 @@ glk_fileref_create_temp(glui32 usage, glui32 rock)
  * <note><title>Chimara</title>
  * <para>
  * Chimara uses a <link 
- * linkend="gtk-GtkFileChooserDialog">GtkFileChooserDialog</link>.
+ * linkend="gtk-GtkFileChooserDialog">GtkFileChooserDialog</link>. The default
+ * starting location for the dialog may be set with glkunix_set_base_file().
  * </para></note>
  *
  * @fmode must be one of these values:
@@ -224,6 +225,9 @@ glk_fileref_create_by_prompt(glui32 usage, glui32 fmode, glui32 rock)
                        return NULL;
        }
        
+       if(glk_data->current_dir)
+               gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(chooser), glk_data->current_dir);
+       
        if(gtk_dialog_run( GTK_DIALOG(chooser) ) != GTK_RESPONSE_ACCEPT)
        {
                gtk_widget_destroy(chooser);
@@ -254,7 +258,8 @@ glk_fileref_create_by_prompt(glui32 usage, glui32 fmode, glui32 rock)
  * </para></note>
  * <note><title>Chimara</title>
  * <para>
- * In Chimara, the file is created in the current working directory.
+ * In Chimara, the file is created in the directory last set by 
+ * glkunix_set_base_file(), and otherwise in the current working directory.
  * </para></note>
  *
  * Since filenames are highly platform-specific, you should use
@@ -284,6 +289,14 @@ glk_fileref_create_by_name(glui32 usage, char *name, glui32 rock)
 {
        g_return_val_if_fail(name != NULL && strlen(name) > 0, NULL);
 
+       /* Do any string-munging here to remove illegal Latin-1 characters from 
+       filename. On ext3, the only illegal characters are '/' and '\0'. */
+       
+       char *ptr = name;
+       while(*ptr++)
+               if(*ptr == '/')
+                       *ptr = '_';
+       
        /* Find out what encoding filenames are in */
        const gchar **charsets; /* Do not free */
        g_get_filename_charsets(&charsets);
@@ -297,14 +310,16 @@ glk_fileref_create_by_name(glui32 usage, char *name, glui32 rock)
                WARNING_S("Error during latin1->filename conversion", error->message);
                return NULL;
        }
-
-       /* Do any string-munging here to remove illegal characters from filename.
-       On ext3, the only illegal characters are '/' and '\0'. TODO: Should this
-       function be allowed to reference files in other directories, or should we
-       disallow '/'? */
-
-       frefid_t f = fileref_new(osname, rock, usage, filemode_ReadWrite);
+       
+       gchar *path;
+       if(glk_data->current_dir)
+               path = g_build_filename(glk_data->current_dir, osname, NULL);
+       else
+               path = g_strdup(osname);
        g_free(osname);
+       
+       frefid_t f = fileref_new(path, rock, usage, filemode_ReadWrite);
+       g_free(path);
        return f;
 }
 
diff --git a/libchimara/glkunix.c b/libchimara/glkunix.c
new file mode 100644 (file)
index 0000000..ee11943
--- /dev/null
@@ -0,0 +1,156 @@
+#include <string.h>
+#include <stdlib.h>
+#include <libchimara/glk.h>
+#include <libchimara/glkstart.h>
+#include "chimara-glk-private.h"
+#include "magic.h"
+#include "fileref.h"
+#include "stream.h"
+
+extern ChimaraGlkPrivate *glk_data;
+
+/**
+ * glkunix_stream_open_pathname:
+ * @pathname: A path to a file, in the system filename encoding. 
+ * @usage: Bitfield with one or more of the <code>fileusage_</code> constants.
+ * @rock: The new stream's rock value.
+ *
+ * Opens an arbitrary file, in read-only mode. Note that this function is
+ * <emphasis>only</emphasis> available during glkunix_startup_code(). It is 
+ * inherently non-portable; it should not and cannot be called from inside 
+ * glk_main().
+ * 
+ * Returns: A new stream, or %NULL if the file operation failed.
+ */
+strid_t
+glkunix_stream_open_pathname(char *pathname, glui32 usage, glui32 rock)
+{
+       if(!glk_data->in_startup)
+               ILLEGAL("glkunix_stream_open_pathname() may only be called from "
+                               "glkunix_startup_code().");
+       
+       g_return_val_if_fail(pathname, NULL);
+       g_return_val_if_fail(strlen(pathname) > 0, NULL);
+       
+       frefid_t fileref = fileref_new(pathname, rock, usage, filemode_Read);
+       return file_stream_new(fileref, filemode_Read, rock, FALSE);
+}
+
+/**
+ * glkunix_set_base_file:
+ * @filename: A path to a file, in the system filename encoding.
+ *
+ * Sets the library's idea of the <quote>current directory</quote> for the 
+ * executing program. The argument should be the name of a file (not a 
+ * directory). When this is set, glk_fileref_create_by_name() will create files 
+ * in the same directory as that file, and glk_fileref_create_by_prompt() will 
+ * base default filenames off of the file. If this is not called, the library 
+ * works in the Unix current working directory, and picks reasonable default 
+ * defaults.
+ */
+void 
+glkunix_set_base_file(char *filename)
+{
+       g_return_if_fail(filename);
+       g_return_if_fail(strlen(filename) > 0);
+       
+       gchar *dirname = g_path_get_dirname(filename);
+       if(!g_file_test(dirname, G_FILE_TEST_IS_DIR))
+       {
+               WARNING_S("Not a directory", dirname);
+               g_free(dirname);
+               return;
+       }
+       
+       glk_data->current_dir = dirname;
+}
+
+/* Internal function: parse the command line, getting only the arguments
+ requested by the plugin in its glkunix_arguments structure. Algorithm copied
+ from CheapGlk by Andrew Plotkin. */
+gboolean
+parse_command_line(glkunix_argumentlist_t glkunix_arguments[], int argc, char *argv[], glkunix_startup_t *data)
+{
+       GSList *arglist = NULL, *iter;
+       int arg;
+       
+       /* Now some argument-parsing. This is probably going to hurt. */
+    for(arg = 1; arg < argc; arg++) 
+       {
+        glkunix_argumentlist_t *argform;
+        char *numptr;
+        
+        for(argform = glkunix_arguments; argform->argtype != glkunix_arg_End; argform++) 
+               {
+            if(argform->name[0] == '\0') 
+                       {
+                if(((argform->argtype == glkunix_arg_ValueFollows ||
+                                   argform->argtype == glkunix_arg_ValueCanFollow) && 
+                                   argv[arg][0] != '-') ||
+                                   (argform->argtype == glkunix_arg_NumberValue &&
+                                       (atoi(argv[arg]) != 0 || argv[arg][0] == '0')))
+                               {
+                    arglist = g_slist_prepend(arglist, argv[arg]);
+                               }
+                               else
+                                       continue;
+            }
+                       
+            else if((argform->argtype == glkunix_arg_NumberValue)
+                && !strncmp(argv[arg], argform->name, strlen(argform->name))
+                && (numptr = argv[arg] + strlen(argform->name))
+                && (atoi(numptr) != 0 || numptr[0] == '0')) 
+                       {
+                arglist = g_slist_prepend(arglist, argv[arg]);
+                       }
+                       
+            else if(strcmp(argv[arg], argform->name) == 0) 
+                       {
+                if(argform->argtype == glkunix_arg_ValueFollows) 
+                               {
+                    if(arg + 1 >= argc) {
+                                               g_slist_free(arglist);
+                                               return FALSE; /* No more arguments, and this one is invalid */
+                                       }
+                    arglist = g_slist_prepend(arglist, argv[arg++]);
+                                       arglist = g_slist_prepend(arglist, argv[arg]);
+                }
+
+                               else if(argform->argtype == glkunix_arg_NoValue) 
+                    arglist = g_slist_prepend(arglist, argv[arg]);
+
+                else if(argform->argtype == glkunix_arg_ValueCanFollow) 
+                               {
+                                       arglist = g_slist_prepend(arglist, argv[arg]);
+                    if(arg + 1 < argc && argv[arg + 1][0] != '-') 
+                        arglist = g_slist_prepend(arglist, argv[++arg]);
+                }
+                
+                               else if(argform->argtype == glkunix_arg_NumberValue) 
+                               {
+                    if(arg + 1 >= argc || (atoi(argv[arg + 1]) == 0 && argv[arg + 1][0] != '0')) 
+                                       {
+                                               g_slist_free(arglist);
+                                               return FALSE;
+                                       }
+                    arglist = g_slist_prepend(arglist, argv[arg++]);
+                                       arglist = g_slist_prepend(arglist, argv[arg]);
+                }
+                else 
+                               {
+                                       g_slist_free(arglist);
+                                       return FALSE;
+                               }
+            }
+               }
+       }
+       
+       data->argc = g_slist_length(arglist) + 1;
+       data->argv = g_new0(char *, data->argc);
+       arglist = g_slist_reverse(arglist);
+       for(iter = arglist, arg = 1; iter; iter = g_slist_next(iter), arg++)
+               data->argv[arg] = g_strdup(iter->data);
+       g_slist_free(arglist);
+       
+       return TRUE;
+}
\ No newline at end of file
diff --git a/libchimara/glkunix.h b/libchimara/glkunix.h
new file mode 100644 (file)
index 0000000..291610e
--- /dev/null
@@ -0,0 +1,9 @@
+#ifndef __GLKUNIX_H__
+#define __GLKUNIX_H__
+
+#include <glib.h>
+#include <libchimara/glkstart.h>
+
+gboolean parse_command_line(glkunix_argumentlist_t glkunix_arguments[], int argc, char *argv[], glkunix_startup_t *data);
+
+#endif
\ No newline at end of file
index 4a1e004cfad42e9235b5a239efb62cd88617ca14..3d7262596011a26541dfaa9171cb46e398320346 100644 (file)
@@ -277,7 +277,7 @@ glk_stream_open_memory_uni(glui32 *buf, glui32 buflen, glui32 fmode, glui32 rock
 }
 
 /* Internal function: create a stream using the given parameters. */
-static strid_t
+strid_t
 file_stream_new(frefid_t fileref, glui32 fmode, glui32 rock, gboolean unicode)
 {
        VALID_FILEREF(fileref, return NULL);
@@ -476,12 +476,3 @@ stream_close_common(strid_t str, stream_result_t *result)
        str->magic = MAGIC_FREE;
        g_free(str);
 }
-
-strid_t
-glkunix_stream_open_pathname(char *pathname, glui32 usage, glui32 rock)
-{
-       printf("making new fileref: %s\n", pathname);
-       frefid_t fileref = fileref_new(pathname, rock, usage, filemode_ReadWrite);
-       printf("makeing new stream:\n");
-       return file_stream_new(fileref, filemode_ReadWrite, rock, FALSE);
-}
index 7bab826b85c8295bc228a549eab69c78af7db9a4..1f9f3c82596e4f45222bcca3a660bb0741108c7f 100644 (file)
@@ -49,5 +49,7 @@ struct glk_stream_struct
 };
 
 G_GNUC_INTERNAL strid_t window_stream_new(winid_t window);
+G_GNUC_INTERNAL strid_t file_stream_new(frefid_t fileref, glui32 fmode, glui32 rock, gboolean unicode);
 G_GNUC_INTERNAL void stream_close_common(strid_t str, stream_result_t *result);
+
 #endif