From: Philip Chimento Date: Sun, 12 Feb 2012 12:58:18 +0000 (+0100) Subject: Merge branch 'glk-074' X-Git-Tag: v0.9~31 X-Git-Url: https://git.stderr.nl/gitweb?p=projects%2Fchimara%2Fchimara.git;a=commitdiff_plain;h=ac51488902551bd4badb553e3f7215220dc68924;hp=f36804297035475779848559f13fc1984ebf8441 Merge branch 'glk-074' --- diff --git a/libchimara/fileref.c b/libchimara/fileref.c index 3db89a2..d4f2043 100644 --- a/libchimara/fileref.c +++ b/libchimara/fileref.c @@ -12,9 +12,10 @@ extern GPrivate *glk_data_key; -/* Internal function: create a fileref using the given parameters. */ +/* Internal function: create a fileref using the given parameters. If @basename +is NULL, compute a basename from @filename. */ frefid_t -fileref_new(gchar *filename, glui32 rock, glui32 usage, glui32 orig_filemode) +fileref_new(char *filename, char *basename, glui32 rock, glui32 usage, glui32 orig_filemode) { g_return_val_if_fail(filename != NULL, NULL); @@ -27,6 +28,10 @@ fileref_new(gchar *filename, glui32 rock, glui32 usage, glui32 orig_filemode) f->disprock = (*glk_data->register_obj)(f, gidisp_Class_Fileref); f->filename = g_strdup(filename); + if(basename) + f->basename = g_strdup(basename); + else + f->basename = g_path_get_basename(filename); f->usage = usage; f->orig_filemode = orig_filemode; @@ -50,8 +55,8 @@ fileref_close_common(frefid_t fref) fref->disprock.ptr = NULL; } - if(fref->filename) - g_free(fref->filename); + g_free(fref->filename); + g_free(fref->basename); fref->magic = MAGIC_FREE; g_free(fref); @@ -147,7 +152,8 @@ glk_fileref_create_temp(glui32 usage, glui32 rock) return NULL; } - frefid_t f = fileref_new(filename, rock, usage, filemode_Write); + /* Pass a basename of "" to ensure that this file can't be repurposed */ + frefid_t f = fileref_new(filename, "", rock, usage, filemode_Write); g_free(filename); return f; } @@ -261,6 +267,48 @@ glk_fileref_create_by_prompt(glui32 usage, glui32 fmode, glui32 rock) return NULL; } + /* Set up a file filter with suggested extensions */ + GtkFileFilter *filter = gtk_file_filter_new(); + switch(usage & fileusage_TypeMask) + { + case fileusage_Data: + gtk_file_filter_set_name(filter, _("Data files (*.glkdata)")); + gtk_file_filter_add_pattern(filter, "*.glkdata"); + break; + case fileusage_SavedGame: + gtk_file_filter_set_name(filter, _("Saved games (*.glksave)")); + gtk_file_filter_add_pattern(filter, "*.glksave"); + break; + case fileusage_InputRecord: + gtk_file_filter_set_name(filter, _("Text files (*.txt)")); + gtk_file_filter_add_pattern(filter, "*.txt"); + break; + case fileusage_Transcript: + gtk_file_filter_set_name(filter, _("Transcript files (*.txt)")); + gtk_file_filter_add_pattern(filter, "*.txt"); + break; + default: + ILLEGAL_PARAM("Unknown file usage: %u", usage); + gdk_threads_leave(); + return NULL; + } + gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(chooser), filter); + + /* Add a "text mode" filter for text files */ + if((usage & fileusage_TypeMask) == fileusage_InputRecord || (usage & fileusage_TypeMask) == fileusage_Transcript) + { + filter = gtk_file_filter_new(); + gtk_file_filter_set_name(filter, _("All text files")); + gtk_file_filter_add_mime_type(filter, "text/plain"); + gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(chooser), filter); + } + + /* Add another non-restricted filter */ + filter = gtk_file_filter_new(); + gtk_file_filter_set_name(filter, _("All files")); + gtk_file_filter_add_pattern(filter, "*"); + gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(chooser), filter); + if(glk_data->current_dir) gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(chooser), glk_data->current_dir); @@ -271,7 +319,7 @@ glk_fileref_create_by_prompt(glui32 usage, glui32 fmode, glui32 rock) return NULL; } gchar *filename = gtk_file_chooser_get_filename( GTK_FILE_CHOOSER(chooser) ); - frefid_t f = fileref_new(filename, rock, usage, fmode); + frefid_t f = fileref_new(filename, NULL, rock, usage, fmode); g_free(filename); gtk_widget_destroy(chooser); @@ -391,8 +439,47 @@ glk_fileref_create_by_name(glui32 usage, char *name, glui32 rock) ChimaraGlkPrivate *glk_data = g_private_get(glk_data_key); /* Do any string-munging here to remove illegal Latin-1 characters from - filename. On ext3, the only illegal characters are '/' and '\0'. */ - g_strdelimit(name, "/", '_'); + filename. On ext3, the only illegal characters are '/' and '\0', but the Glk + spec calls for removing any other tricky characters. */ + char *buf = g_malloc(strlen(name)); + char *ptr, *filename, *extension; + int len; + for(ptr = name, len = 0; *ptr && *ptr != '.'; ptr++) + { + switch(*ptr) + { + case '"': case '\\': case '/': case '>': case '<': + case ':': case '|': case '?': case '*': + break; + default: + buf[len++] = *ptr; + } + } + buf[len] = '\0'; + + /* If there is nothing left, make the name "null" */ + if(len == 0) { + strcpy(buf, "null"); + len = strlen(buf); + } + + switch(usage & fileusage_TypeMask) + { + case fileusage_Data: + extension = ".glkdata"; + break; + case fileusage_SavedGame: + extension = ".glksave"; + break; + case fileusage_InputRecord: + case fileusage_Transcript: + extension = ".txt"; + break; + default: + ILLEGAL_PARAM("Unknown file usage: %u", usage); + return NULL; + } + filename = g_strconcat(buf, extension, NULL); /* Find out what encoding filenames are in */ const gchar **charsets; /* Do not free */ @@ -400,8 +487,7 @@ glk_fileref_create_by_name(glui32 usage, char *name, glui32 rock) /* Convert name to that encoding */ GError *error = NULL; - gchar *osname = g_convert(name, -1, charsets[0], "ISO-8859-1", NULL, NULL, - &error); + char *osname = g_convert(filename, -1, charsets[0], "ISO-8859-1", NULL, NULL, &error); if(osname == NULL) { WARNING_S("Error during latin1->filename conversion", error->message); @@ -415,8 +501,9 @@ glk_fileref_create_by_name(glui32 usage, char *name, glui32 rock) path = g_strdup(osname); g_free(osname); - frefid_t f = fileref_new(path, rock, usage, filemode_ReadWrite); + frefid_t f = fileref_new(path, buf, rock, usage, filemode_ReadWrite); g_free(path); + g_free(buf); return f; } @@ -455,7 +542,7 @@ frefid_t glk_fileref_create_from_fileref(glui32 usage, frefid_t fref, glui32 rock) { VALID_FILEREF(fref, return NULL); - return fileref_new(fref->filename, rock, usage, fref->orig_filemode); + return fileref_new(fref->filename, fref->basename, rock, usage, fref->orig_filemode); } /** diff --git a/libchimara/fileref.h b/libchimara/fileref.h index c2d8f46..03ab4fe 100644 --- a/libchimara/fileref.h +++ b/libchimara/fileref.h @@ -22,10 +22,11 @@ struct glk_fileref_struct /* Fileref parameters */ gchar *filename; /* Always stored in the default filename encoding, not UTF8 or Latin-1 */ + char *basename; /* Name from which real filename was derived */ glui32 orig_filemode; /* Used to check if the user gets a fileref in read mode and then tries to open it in write mode */ glui32 usage; }; -G_GNUC_INTERNAL frefid_t fileref_new(gchar *filename, glui32 rock, glui32 usage, glui32 orig_filemode); +G_GNUC_INTERNAL frefid_t fileref_new(char *filename, char *basename, glui32 rock, glui32 usage, glui32 orig_filemode); #endif diff --git a/libchimara/gestalt.c b/libchimara/gestalt.c index 55ff57c..1dad9d9 100644 --- a/libchimara/gestalt.c +++ b/libchimara/gestalt.c @@ -123,6 +123,7 @@ glk_gestalt_ext(glui32 sel, glui32 val, glui32 *arr, glui32 arrlen) case gestalt_UnicodeNorm: case gestalt_LineInputEcho: case gestalt_LineTerminators: + case gestalt_ResourceStream: return 1; /* Capabilities supported if compiled with GStreamer */ @@ -138,8 +139,7 @@ glk_gestalt_ext(glui32 sel, glui32 val, glui32 *arr, glui32 arrlen) #endif /* Unsupported capabilities */ - case gestalt_ResourceStream: - return 0; + /* None! */ /* Selector not supported */ default: diff --git a/libchimara/glkunix.c b/libchimara/glkunix.c index 284fc1c..ddaf848 100644 --- a/libchimara/glkunix.c +++ b/libchimara/glkunix.c @@ -36,7 +36,7 @@ glkunix_stream_open_pathname_gen(char *pathname, glui32 writemode, glui32 textmo g_return_val_if_fail(pathname, NULL); g_return_val_if_fail(strlen(pathname) > 0, NULL); - frefid_t fileref = fileref_new(pathname, rock, + frefid_t fileref = fileref_new(pathname, NULL, rock, textmode? fileusage_TextMode : fileusage_BinaryMode, writemode? filemode_Write : filemode_Read); return file_stream_new(fileref, writemode? filemode_Write : filemode_Read, rock, FALSE); @@ -67,7 +67,7 @@ glkunix_stream_open_pathname(char *pathname, glui32 textmode, glui32 rock) g_return_val_if_fail(pathname, NULL); g_return_val_if_fail(strlen(pathname) > 0, NULL); - frefid_t fileref = fileref_new(pathname, rock, textmode? fileusage_TextMode : fileusage_BinaryMode, filemode_Read); + frefid_t fileref = fileref_new(pathname, NULL, rock, textmode? fileusage_TextMode : fileusage_BinaryMode, filemode_Read); return file_stream_new(fileref, filemode_Read, rock, FALSE); } @@ -190,4 +190,4 @@ parse_command_line(glkunix_argumentlist_t glkunix_arguments[], int argc, char *a g_slist_free(arglist); return TRUE; -} \ No newline at end of file +} diff --git a/libchimara/stream.c b/libchimara/stream.c index f9a6cf5..0b1e2d8 100644 --- a/libchimara/stream.c +++ b/libchimara/stream.c @@ -1,10 +1,13 @@ +#include #include "stream.h" #include "fileref.h" #include "magic.h" +#include "gi_blorb.h" #include #include #include #include +#include #include "chimara-glk-private.h" extern GPrivate *glk_data_key; @@ -516,8 +519,58 @@ glk_stream_open_file_uni(frefid_t fileref, glui32 fmode, glui32 rock) strid_t glk_stream_open_resource(glui32 filenum, glui32 rock) { - g_warning("Not implemented"); - return NULL; + /* Adapted from CheapGlk */ + strid_t str; + gboolean isbinary; + giblorb_err_t err; + giblorb_result_t res; + giblorb_map_t *map = giblorb_get_resource_map(); + if(map == NULL) { + WARNING(_("Could not create resource stream, because there was no " + "resource map.")); + return NULL; /* Not running from a blorb file */ + } + + err = giblorb_load_resource(map, giblorb_method_Memory, &res, giblorb_ID_Data, filenum); + if(err) { + WARNING_S(_("Could not create resource stream, because the resource " + "could not be loaded"), giblorb_get_error_message(err)); + return 0; /* Not found, or some other error */ + } + + /* We'll use the in-memory copy of the chunk data as the basis for + our new stream. It's important to not call chunk_unload() until + the stream is closed (and we won't). + + This will be memory-hoggish for giant data chunks, but I don't + expect giant data chunks at this point. A more efficient model + would be to use the file on disk, but this requires some hacking + into the file stream code (we'd need to open a new FILE*) and + I don't feel like doing that. */ + + if(res.chunktype == giblorb_ID_TEXT) + isbinary = FALSE; + else if(res.chunktype == giblorb_ID_BINA) + isbinary = TRUE; + else { + WARNING(_("Could not create resource stream, because chunk was of " + "unknown type.")); + return NULL; /* Unknown chunk type */ + } + + str = stream_new_common(rock); + str->type = STREAM_TYPE_RESOURCE; + str->file_mode = filemode_Read; + str->binary = isbinary; + + if (res.data.ptr && res.length) { + str->buffer = res.data.ptr; + str->mark = 0; + str->buflen = res.length; + str->endmark = str->buflen; + } + + return str; } /** @@ -542,8 +595,16 @@ glk_stream_open_resource(glui32 filenum, glui32 rock) strid_t glk_stream_open_resource_uni(glui32 filenum, glui32 rock) { - g_warning("Not implemented"); - return NULL; + /* Adapted from CheapGlk */ + /* We have been handed an array of bytes. (They're big-endian + four-byte chunks, or perhaps a UTF-8 byte sequence, rather than + native-endian four-byte integers). So we drop it into str->buffer, + rather than str->ubuffer -- we'll have to do the translation in the + get() functions. */ + strid_t str = glk_stream_open_resource(filenum, rock); + if(str != NULL) + str->unicode = TRUE; + return str; } /** @@ -592,6 +653,12 @@ glk_stream_close(strid_t str, stream_result_t *result) IO_WARNING( "Failed to close file", str->filename, g_strerror(errno) ); g_free(str->filename); break; + + case STREAM_TYPE_RESOURCE: + /* Shouldn't free the chunk; someone else might be using it. It will + be freed when the resource map is freed. */ + break; + default: ILLEGAL_PARAM("Unknown stream type: %u", str->type); return; diff --git a/libchimara/stream.h b/libchimara/stream.h index 7ff7618..33497ce 100644 --- a/libchimara/stream.h +++ b/libchimara/stream.h @@ -12,7 +12,8 @@ enum StreamType { STREAM_TYPE_WINDOW, STREAM_TYPE_MEMORY, - STREAM_TYPE_FILE + STREAM_TYPE_FILE, + STREAM_TYPE_RESOURCE }; /** @@ -36,18 +37,20 @@ struct glk_stream_struct enum StreamType type; /* Specific to window stream: the window this stream is connected to */ winid_t window; - /* For memory and file streams */ + /* For memory, file, and resource streams */ gboolean unicode; - /* Specific to memory streams */ - gchar *buffer; + /* For file and resource streams */ + gboolean binary; + /* For memory and resource streams */ + char *buffer; glui32 *ubuffer; glui32 mark; glui32 endmark; glui32 buflen; + /* Specific to memory streams */ gidispatch_rock_t buffer_rock; /* Specific to file streams */ FILE *file_pointer; - gboolean binary; gchar *filename; /* Displayable filename in UTF-8 for error handling */ glui32 lastop; /* 0, filemode_Write, or filemode_Read */ diff --git a/libchimara/strio.c b/libchimara/strio.c index 41146f2..d8dda82 100644 --- a/libchimara/strio.c +++ b/libchimara/strio.c @@ -1,3 +1,4 @@ +#include #include "charset.h" #include "magic.h" #include "stream.h" @@ -6,6 +7,7 @@ #include #include #include +#include /* Internal function: ensure that an fseek() is called on a file pointer in between reading and writing operations, and vice versa. This will only come up @@ -346,6 +348,9 @@ write_buffer_to_stream(strid_t str, gchar *buf, glui32 len) str->write_count += len; break; + case STREAM_TYPE_RESOURCE: + ILLEGAL(_("Writing to a resource stream is illegal.")); + break; default: ILLEGAL_PARAM("Unknown stream type: %u", str->type); } @@ -444,6 +449,9 @@ write_buffer_to_stream_uni(strid_t str, glui32 *buf, glui32 len) str->write_count += len; break; + case STREAM_TYPE_RESOURCE: + ILLEGAL(_("Writing to a resource stream is illegal.")); + break; default: ILLEGAL_PARAM("Unknown stream type: %u", str->type); } @@ -623,6 +631,58 @@ read_utf8_char_from_file(strid_t str) return charresult; } +/* Internal function: Read one UTF-8 character, which may be more than one byte, +from a memory stream @str, and return it as a Unicode code point. */ +static glsi32 +read_utf8_char_from_buffer(strid_t str) +{ + size_t foo; + gunichar charresult = (gunichar)-2; + char *buffer = str->buffer + str->mark; + size_t maxlen = str->buflen - str->mark; + + if(maxlen == 0) + return -1; + + for(foo = 1; foo <= maxlen; foo++) + { + charresult = g_utf8_get_char_validated(buffer, foo); + /* charresult is -1 if invalid, -2 if incomplete, and the + Unicode code point otherwise */ + if(charresult != (gunichar)-2) + break; + } + str->mark += foo; + str->read_count++; + + /* Return -1 on EOS */ + if(charresult == (gunichar)-2) + return -1; + /* Silently return unknown characters as 0xFFFD, Replacement Character */ + if(charresult == (gunichar)-1) + return 0xFFFD; + return charresult; +} + +/* Internal function: Read one big-endian four-byte character from memory and +return it as a Unicode code point, or -1 on EOF */ +static glsi32 +read_ucs4be_char_from_buffer(strid_t str) +{ + glui32 ch = str->buffer[str->mark++]; + if(str->mark >= str->buflen) + return -1; + ch = (ch << 8) | (str->buffer[str->mark++] & 0xFF); + if(str->mark >= str->buflen) + return -1; + ch = (ch << 8) | (str->buffer[str->mark++] & 0xFF); + if(str->mark >= str->buflen) + return -1; + ch = (ch << 8) | (str->buffer[str->mark++] & 0xFF); + str->read_count++; + return ch; +} + /* Internal function: Tell whether this code point is a Unicode newline. The file pointer and eight-bit flag are included in case the newline is a CR (U+000D). If the next character is LF (U+000A) then it also belongs to the @@ -653,6 +713,18 @@ get_char_stream_common(strid_t str) { switch(str->type) { + case STREAM_TYPE_RESOURCE: + if(str->unicode) + { + if(!str->buffer || str->mark >= str->buflen) + return -1; + if(str->binary) + /* Cheap big-endian stream */ + return read_ucs4be_char_from_buffer(str); + /* slightly less cheap UTF8 stream */ + return read_utf8_char_from_buffer(str); + } + /* for text streams, fall through to memory case */ case STREAM_TYPE_MEMORY: if(str->unicode) { @@ -785,6 +857,24 @@ glk_get_buffer_stream(strid_t str, char *buf, glui32 len) switch(str->type) { + case STREAM_TYPE_RESOURCE: + { + int copycount = 0; + if(str->unicode) + { + while(copycount < len && str->buffer && str->mark < str->buflen) + { + glui32 ch; + if(str->binary) + ch = read_ucs4be_char_from_buffer(str); + else + ch = read_utf8_char_from_buffer(str); + buf[copycount++] = (ch > 0xFF)? '?' : (char)ch; + } + return copycount; + } + /* for text streams, fall through to memory case */ + } case STREAM_TYPE_MEMORY: { int copycount = 0; @@ -884,6 +974,24 @@ glk_get_buffer_stream_uni(strid_t str, glui32 *buf, glui32 len) switch(str->type) { + case STREAM_TYPE_RESOURCE: + { + int copycount = 0; + if(str->unicode) + { + while(copycount < len && str->buffer && str->mark < str->buflen) + { + glui32 ch; + if(str->binary) + ch = read_ucs4be_char_from_buffer(str); + else + ch = read_utf8_char_from_buffer(str); + buf[copycount++] = ch; + } + return copycount; + } + /* for text streams, fall through to memory case */ + } case STREAM_TYPE_MEMORY: { int copycount = 0; @@ -992,6 +1100,40 @@ glk_get_line_stream(strid_t str, char *buf, glui32 len) switch(str->type) { + case STREAM_TYPE_RESOURCE: + { + int copycount = 0; + if(str->unicode) + { + /* Do it character-by-character */ + while(copycount < len - 1 && str->buffer && str->mark < str->buflen) + { + glsi32 ch; + if(str->binary) + ch = read_ucs4be_char_from_buffer(str); + else + ch = read_utf8_char_from_buffer(str); + /* Check for Unicode newline; slightly different than + in file streams */ + if(ch == 0x0A || ch == 0x85 || ch == 0x0C || ch == 0x2028 || ch == 0x2029) + { + buf[copycount++] = '\n'; + break; + } + if(ch == 0x0D) + { + if(str->buffer[str->mark] == 0x0A) + str->mark++; /* skip past next newline */ + buf[copycount++] = '\n'; + break; + } + buf[copycount++] = (ch > 0xFF)? '?' : (char)ch; + } + buf[copycount] = '\0'; + return copycount; + } + /* for text streams, fall through to the memory case */ + } case STREAM_TYPE_MEMORY: { int copycount = 0; @@ -1129,6 +1271,40 @@ glk_get_line_stream_uni(strid_t str, glui32 *buf, glui32 len) switch(str->type) { + case STREAM_TYPE_RESOURCE: + { + int copycount = 0; + if(str->unicode) + { + /* Do it character-by-character */ + while(copycount < len - 1 && str->buffer && str->mark < str->buflen) + { + glsi32 ch; + if(str->binary) + ch = read_ucs4be_char_from_buffer(str); + else + ch = read_utf8_char_from_buffer(str); + /* Check for Unicode newline; slightly different than + in file streams */ + if(ch == 0x0A || ch == 0x85 || ch == 0x0C || ch == 0x2028 || ch == 0x2029) + { + buf[copycount++] = '\n'; + break; + } + if(ch == 0x0D) + { + if(str->ubuffer[str->mark] == 0x0A) + str->mark++; /* skip past next newline */ + buf[copycount++] = '\n'; + break; + } + buf[copycount++] = ch; + } + buf[copycount] = '\0'; + return copycount; + } + /* for text streams, fall through to the memory case */ + } case STREAM_TYPE_MEMORY: { int copycount = 0; @@ -1297,6 +1473,7 @@ glk_stream_get_position(strid_t str) switch(str->type) { case STREAM_TYPE_MEMORY: + case STREAM_TYPE_RESOURCE: return str->mark; case STREAM_TYPE_FILE: return ftell(str->file_pointer); @@ -1346,6 +1523,7 @@ glk_stream_set_position(strid_t str, glsi32 pos, glui32 seekmode) switch(str->type) { + case STREAM_TYPE_RESOURCE: case STREAM_TYPE_MEMORY: switch(seekmode) { diff --git a/tests/externalfile.ulx b/tests/externalfile.ulx index 7f0b40e..91f66ce 100644 Binary files a/tests/externalfile.ulx and b/tests/externalfile.ulx differ diff --git a/tests/glulxercise.ui b/tests/glulxercise.ui index 264de51..1cb21a4 100644 --- a/tests/glulxercise.ui +++ b/tests/glulxercise.ui @@ -1,6 +1,6 @@ - + 6 @@ -189,6 +189,10 @@ MemStreamTest memstreamtest.ulx + + ResStreamTest + resstreamtest.gblorb + UniCaseTest unicasetest.ulx diff --git a/tests/resstreamtest.gblorb b/tests/resstreamtest.gblorb new file mode 100644 index 0000000..123c0ac Binary files /dev/null and b/tests/resstreamtest.gblorb differ