From 46271bc1f196ef9c6d0c69a84a55c9eac1deb8b3 Mon Sep 17 00:00:00 2001 From: fliep Date: Sun, 24 May 2009 18:11:03 +0000 Subject: [PATCH] Implemented Unix startup code, argument parsing, and platform-dependent functions --- libchimara/Makefile.am | 3 +- libchimara/chimara-glk-private.h | 12 +++ libchimara/chimara-glk.c | 51 ++++++++-- libchimara/fileref.c | 33 +++++-- libchimara/glkunix.c | 156 +++++++++++++++++++++++++++++++ libchimara/glkunix.h | 9 ++ libchimara/stream.c | 11 +-- libchimara/stream.h | 2 + 8 files changed, 248 insertions(+), 29 deletions(-) create mode 100644 libchimara/glkunix.c create mode 100644 libchimara/glkunix.h diff --git a/libchimara/Makefile.am b/libchimara/Makefile.am index 87f0498..fa18747 100644 --- a/libchimara/Makefile.am +++ b/libchimara/Makefile.am @@ -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 diff --git a/libchimara/chimara-glk-private.h b/libchimara/chimara-glk-private.h index 2cfc523..e1067b2 100644 --- a/libchimara/chimara-glk-private.h +++ b/libchimara/chimara-glk-private.h @@ -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) \ diff --git a/libchimara/chimara-glk.c b/libchimara/chimara-glk.c index fc4c446..2c61dac 100644 --- a/libchimara/chimara-glk.c +++ b/libchimara/chimara-glk.c @@ -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 glk.h + * @argc: Number of command line arguments in @argv + * @argv: Array of command line arguments to pass to the plugin * @error: location to store a GError, 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); diff --git a/libchimara/fileref.c b/libchimara/fileref.c index 08ae584..516af02 100644 --- a/libchimara/fileref.c +++ b/libchimara/fileref.c @@ -137,7 +137,8 @@ glk_fileref_create_temp(glui32 usage, glui32 rock) * Chimara * * Chimara uses a GtkFileChooserDialog. + * linkend="gtk-GtkFileChooserDialog">GtkFileChooserDialog. The default + * starting location for the dialog may be set with glkunix_set_base_file(). * * * @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) * * Chimara * - * 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. * * * 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 index 0000000..ee11943 --- /dev/null +++ b/libchimara/glkunix.c @@ -0,0 +1,156 @@ +#include +#include +#include +#include +#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 fileusage_ constants. + * @rock: The new stream's rock value. + * + * Opens an arbitrary file, in read-only mode. Note that this function is + * only 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 current directory 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 index 0000000..291610e --- /dev/null +++ b/libchimara/glkunix.h @@ -0,0 +1,9 @@ +#ifndef __GLKUNIX_H__ +#define __GLKUNIX_H__ + +#include +#include + +gboolean parse_command_line(glkunix_argumentlist_t glkunix_arguments[], int argc, char *argv[], glkunix_startup_t *data); + +#endif \ No newline at end of file diff --git a/libchimara/stream.c b/libchimara/stream.c index 4a1e004..3d72625 100644 --- a/libchimara/stream.c +++ b/libchimara/stream.c @@ -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); -} diff --git a/libchimara/stream.h b/libchimara/stream.h index 7bab826..1f9f3c8 100644 --- a/libchimara/stream.h +++ b/libchimara/stream.h @@ -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 -- 2.30.2