fileref.c fileref.h \
gestalt.c \
glk.c glk.h \
+ glkunix.c glkunix.h \
magic.c magic.h \
mouse.c \
input.c input.h \
libchimara_includedir = $(includedir)/chimara/chimara
libchimara_include_HEADERS = chimara-glk.h
-EXTRA_DIST = doc.c
+EXTRA_DIST = doc.c glkstart.c
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 */
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 */
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 */
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) \
#include "abort.h"
#include "window.h"
#include "glkstart.h"
+#include "glkunix.h"
#define CHIMARA_GLK_MIN_WIDTH 0
#define CHIMARA_GLK_MIN_HEIGHT 0
*/
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,
priv->current_stream = NULL;
priv->stream_list = NULL;
priv->timer_id = 0;
+ priv->in_startup = FALSE;
+ priv->current_dir = NULL;
}
static void
/* 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);
}
* @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
ChimaraGlkPrivate *priv = CHIMARA_GLK_PRIVATE(glk);
-
/* Open the module to run */
glk_main_t glk_main;
glkunix_startup_code_t glkunix_startup_code;
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);
* <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:
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);
* </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
{
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);
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;
}
--- /dev/null
+#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
--- /dev/null
+#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
}
/* 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);
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);
-}
};
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