Merge branch 'master' of ssh://git.stderr.nl/projects/chimara/chimara
authorMarijn van Vliet <w.m.vanvliet@student.utwente.nl>
Tue, 28 Aug 2012 08:57:54 +0000 (10:57 +0200)
committerMarijn van Vliet <w.m.vanvliet@student.utwente.nl>
Tue, 28 Aug 2012 08:57:54 +0000 (10:57 +0200)
57 files changed:
configure.ac
docs/reference/blorb.sgml
docs/reference/chimara-docs.sgml
docs/reference/chimara-sections.txt
docs/reference/glk-front-matter.sgml
docs/reference/glk-introduction.sgml
docs/reference/glk-sound-resources.sgml
interpreters/glulxe/README
interpreters/glulxe/exec.c
interpreters/glulxe/funcs.c
interpreters/glulxe/gestalt.c
interpreters/glulxe/glkop.c
interpreters/glulxe/glulxe.h
interpreters/glulxe/heap.c
interpreters/glulxe/profile-analyze.py
interpreters/glulxe/profile.c
interpreters/glulxe/search.c
interpreters/glulxe/serial.c
interpreters/glulxe/string.c
interpreters/glulxe/unixstrt.c
libchimara/Makefile.am
libchimara/chimara-glk.c
libchimara/chimara-glk.h
libchimara/chimara-if.c
libchimara/chimara-if.h
libchimara/chimara.vapi [deleted file]
libchimara/doc.c
libchimara/fileref.c
libchimara/fileref.h
libchimara/gestalt.c
libchimara/gi_blorb.c
libchimara/gi_blorb.h
libchimara/gi_dispa.c
libchimara/gi_dispa.h
libchimara/glk.h
libchimara/glkunix.c
libchimara/graphics.c
libchimara/schannel.c
libchimara/schannel.h
libchimara/stream.c
libchimara/stream.h
libchimara/strio.c
libchimara/window.c
player/Makefile.am
player/chimara.menus [new file with mode: 0644]
player/chimara.menus.in [deleted file]
player/config.py.in [new file with mode: 0644]
player/main.c
player/player.py [new file with mode: 0644]
tests/chinesedicttest.ulx [new file with mode: 0644]
tests/dictflagtest.ulx [new file with mode: 0644]
tests/dictflagtest.z5 [new file with mode: 0644]
tests/externalfile.ulx
tests/glulxercise.ui
tests/resstreamtest.gblorb [new file with mode: 0644]
tests/soundtest.c
tests/unisourcetest.ulx [new file with mode: 0644]

index 749c7443219267d234917192925692022f1fa6d7..bb97b7025f0bc0ba266ed0c9230a32de746afa48 100644 (file)
@@ -30,7 +30,7 @@ AC_SUBST(LT_VERSION_INFO)
 # Recommended Glib version: at least 2.16
 GTK_REQUIRED_VERSION=2.6
 GLIB_REQUIRED_VERSION=2.6
-GTK_DOC_REQUIRED_VERSION=1.9
+GTK_DOC_REQUIRED_VERSION=1.12
 AC_SUBST(GTK_REQUIRED_VERSION)
 AC_SUBST(GLIB_REQUIRED_VERSION)
 AC_SUBST(GTK_DOC_REQUIRED_VERSION)
@@ -85,18 +85,6 @@ AC_ARG_ENABLE([iliad],
        [enable_iliad=no])
 AM_CONDITIONAL(TARGET_ILIAD, $TEST "x$enable_iliad" = xyes)
 
-### BUILD WITHOUT RECENT FILES MANAGER #########################################
-# (to work around a bug on OS X)
-AC_ARG_ENABLE([recent],
-       [AS_HELP_STRING([--disable-recent],
-               [Omit recent files menu (to work around a bug on OS X])],
-       [],
-       [enable_recent=yes])
-AS_IF([$TEST "x$enable_recent" = "xyes"],
-       [OPEN_RECENT_MENU_ITEM="<menuitem action=\"recent\"/>"],
-       [OPEN_RECENT_MENU_ITEM="<!--  <menuitem action=\"recent\"/>-->"])
-AC_SUBST(OPEN_RECENT_MENU_ITEM)
-
 ### RPM CONFIGURATION ##########################################################
 # --enable-rpm requires rpm and rpmbuild
 AC_PATH_PROG([RPMBUILD], [rpmbuild], [notfound])
@@ -120,6 +108,11 @@ AS_IF([$TEST "x$with_gstreamer" != xno],
        [AC_DEFINE([GSTREAMER_SOUND], [1], [Define to enable sound support with GStreamer])
        SOUND_MODULE="gstreamer-0.10 >= 0.10.12"])
 
+### WHETHER TO GENERATE A .VAPI FILE ##########################################
+# Requires vapigen
+AC_PATH_PROG([VAPIGEN], [vapigen], [notfound])
+AM_CONDITIONAL(BUILDING_VAPI, $TEST "x$VAPIGEN" != xnotfound)
+
 ### CHECK FOR LIBRARIES #######################################################
 
 # Libraries needed to build Chimara library
@@ -188,7 +181,7 @@ interpreters/glulxe/Makefile
 interpreters/git/Makefile
 tests/Makefile
 player/Makefile
-player/chimara.menus
+player/config.py
 docs/Makefile
 docs/reference/Makefile
 docs/reference/version.xml
index 28659f8d418c49b70f6edc598caec43c667cb7f0..fb0cdf88dc9d5ffd3e5bda37876802aa5f4a5fce 100644 (file)
@@ -29,7 +29,7 @@ This is for historical reasons; Infocom's Z-machine architecture used this schem
 </para>
 <para>
 For the complete Blorb specification and tools for Blorb file manipulation, see:
-<ulink role="online-location" url="http://www.eblong.com/zarf/blorb/">http://www.eblong.com/zarf/blorb/</ulink>
+<ulink role="online-location" url="http://eblong.com/zarf/blorb/"/>
 </para>
 <refsect2 id="chimara-How-This-Works">
 <title>How This Works</title>
index af494a7332962aa823c35fbbb6f863e6e65021c9..7f739e70ce85e754e4abe71f82a6cb7fb0bc3cfd 100644 (file)
@@ -19,7 +19,7 @@
   </reference>
   
   <reference id="chimara-glk-api-spec">
-    <title>Glk API Specification, version 0.7.0</title>
+    <title>Glk API Specification, version 0.7.4</title>
     <xi:include href="glk-front-matter.sgml"/>
     
     <!-- Chapter 0. Introduction -->
index 809f3a591208594c10ab3f0560c3805a3c3a6acb..09b2dcf352a8ce2f6ce1055d76471c24ed624692 100644 (file)
@@ -20,6 +20,7 @@ chimara_glk_set_css_to_default
 chimara_glk_set_css_from_file
 chimara_glk_set_css_from_string
 chimara_glk_run
+chimara_glk_run_file
 chimara_glk_stop
 chimara_glk_wait
 chimara_glk_unload_plugin
@@ -54,6 +55,7 @@ chimara_if_new
 chimara_if_set_preferred_interpreter
 chimara_if_get_preferred_interpreter
 chimara_if_run_game
+chimara_if_run_game_file
 chimara_if_get_format
 chimara_if_get_interpreter
 <SUBSECTION Standard>
@@ -130,6 +132,7 @@ gestalt_MouseInput
 gestalt_Timer
 gestalt_Graphics
 gestalt_DrawImage
+gestalt_Sound2
 gestalt_Sound
 gestalt_SoundVolume
 gestalt_SoundNotify
@@ -138,6 +141,8 @@ gestalt_HyperlinkInput
 gestalt_SoundMusic
 gestalt_GraphicsTransparency
 gestalt_DateTime
+gestalt_ResourceStream
+GLK_MODULE_RESOURCE_STREAM
 </SECTION>
 
 <SECTION>
@@ -265,6 +270,7 @@ evtype_Arrange
 evtype_Redraw
 evtype_SoundNotify
 evtype_Hyperlink
+evtype_VolumeNotify
 </SECTION>
 
 <SECTION>
@@ -415,6 +421,8 @@ glk_stream_open_memory
 glk_stream_open_memory_uni
 glk_stream_open_file
 glk_stream_open_file_uni
+glk_stream_open_resource
+glk_stream_open_resource_uni
 </SECTION>
 
 <SECTION>
@@ -494,6 +502,7 @@ GLK_MODULE_IMAGE
 <FILE>glk-sound-channels</FILE>
 <TITLE>Creating and Destroying Sound Channels</TITLE>
 glk_schannel_create
+glk_schannel_create_ext
 glk_schannel_destroy
 </SECTION>
 
@@ -502,8 +511,12 @@ glk_schannel_destroy
 <TITLE>Playing Sounds</TITLE>
 glk_schannel_play
 glk_schannel_play_ext
+glk_schannel_play_multi
 glk_schannel_stop
+glk_schannel_pause
+glk_schannel_unpause
 glk_schannel_set_volume
+glk_schannel_set_volume_ext
 glk_sound_load_hint
 </SECTION>
 
@@ -518,6 +531,7 @@ glk_schannel_get_rock
 <FILE>glk-sound-testing</FILE>
 <TITLE>Testing for Sound Capabilities</TITLE>
 GLK_MODULE_SOUND
+GLK_MODULE_SOUND2
 </SECTION>
 
 <SECTION>
@@ -636,12 +650,15 @@ giblorb_count_resources
 giblorb_method_DontLoad
 giblorb_method_Memory
 giblorb_method_FilePos
-giblorb_ID_Snd
 giblorb_ID_Exec
+giblorb_ID_Snd
 giblorb_ID_Pict
+giblorb_ID_Data
 giblorb_ID_Copyright
 giblorb_ID_AUTH
 giblorb_ID_ANNO
+giblorb_ID_TEXT
+giblorb_ID_BINA
 <SUBSECTION Private>
 giblorb_make_id
 </SECTION>
index 59ef6b380d8b51a6c1fea33b974bb796c0ffcb5f..102fa0ca000deba90da46c8103d7139edaa250f5 100644 (file)
@@ -4,7 +4,7 @@
 ]>
 <referenceinfo>
 <title>Glk API Specification</title>
-<subtitle>API version 0.7.2</subtitle>
+<subtitle>API version 0.7.4</subtitle>
 <author>
   <personname>
     <firstname>Andrew</firstname>
@@ -13,7 +13,7 @@
   <email>erkyrath@eblong.com</email>
 </author>
 <copyright>
-  <year>1998&ndash;2011</year>
+  <year>1998&ndash;2012</year>
   <holder>Andrew Plotkin</holder>
 </copyright>
 <legalnotice>
@@ -26,6 +26,6 @@ Anyone is free to write programs that use the Glk API or libraries that implemen
 </para>
 </legalnotice>
 <releaseinfo>
-The authors of the Chimara library have adapted this document to better fit the format of a GtkDoc reference manual. They have also added notes specific to Chimara's implementation of the Glk API. The original API specification and further Glk information can be found at: <ulink url="http://www.eblong.com/zarf/glk/">http://www.eblong.com/zarf/glk/</ulink>
+The authors of the Chimara library have adapted this document to better fit the format of a GtkDoc reference manual. They have also added notes specific to Chimara's implementation of the Glk API. The original API specification and further Glk information can be found at: <ulink url="http://eblong.com/zarf/glk/"/>
 </releaseinfo>
 </referenceinfo>
index ec2897934c1a1d1549cc1cad9dd1b74dfcbcc7e1..aeacbec18fbbe655169157ef1e6caa5bb59bf4cf 100644 (file)
@@ -45,7 +45,7 @@ You can think of Glk as an IF virtual machine, without the virtual machine part.
 An IF virtual machine has been designed specifically to go along with Glk. This VM, called Glulx, uses Glk as its interface; each Glk call corresponds to an input/output opcode of the VM.
 </para>
 <para>
-For more discussion of this approach, see <link linkend="chimara-Glk-and-the-Virtual-Machine">Glk and the Virtual Machine</link>. Glulx is documented at <ulink url="http://www.eblong.com/zarf/glulx/">http://www.eblong.com/zarf/glulx</ulink>.
+For more discussion of this approach, see <link linkend="chimara-Glk-and-the-Virtual-Machine">Glk and the Virtual Machine</link>. Glulx is documented at <ulink url="http://eblong.com/zarf/glulx/">http://eblong.com/zarf/glulx</ulink>.
 </para>
 <para>
 Of course, Glk can be used with other IF systems. The advantage of Glulx is that it provides the game author with direct and complete access to the Glk API. Other IF systems typically have an built-in abstract I/O API, which maps only partially onto Glk. For these systems, Glk tends to be a least-common-denominator interface: highly portable, but not necessarily featureful. (Even if Glk has a feature, it may not be available through the layers of abstraction.) 
index aaf4fd4154d7496b3f15b6db8078ee640c27d5ea..857620394ac6134196f3247cfcb456949bbc37e2 100644 (file)
@@ -29,7 +29,7 @@ A resource can theoretically contain any kind of sound data, of any length. A re
 A resource can also contain two or more channels of sound (stereo data). Do not confuse such in-sound channels with Glk sound channels. A single Glk sound channel suffices to play any sound, even stereo sounds.
 </para>
 <note><para>
-Again, Blorb is the official resource-storage format of Glk. Sounds in Blorb files can be encoded as AIFF, MOD, or MOD song data. See the Blorb specification for details.
+Again, Blorb is the official resource-storage format of Glk. Sounds in Blorb files can be encoded as Ogg, AIFF, or MOD. See the Blorb specification for details.
 </para></note>
 </refsect1>
-</refentry>
\ No newline at end of file
+</refentry>
index e903d1e7e4fdbe8841ebd1c8c1819fdbeeaa0ca1..952e8dd888c34cc04268660b3b74323918984ea5 100644 (file)
@@ -1,5 +1,5 @@
 Glulxe: the Glulx VM interpreter
-Version 0.4.###
+Version 0.4.7
 
     Designed by Andrew Plotkin <erkyrath@eblong.com>
     http://eblong.com/zarf/glulx/index.html
@@ -34,9 +34,12 @@ comment out the "#define FLOAT_SUPPORT" line in glulxe.h.
 
 * Version
 
-###:
+0.4.7:
     Abstracted powf() to an osdepend wrapper. (Needed for Windows.)
     Fixed a @ceil bug, for some C math libraries.
+    Improved the profiling system in several ways.
+    Fixed a bug in glkop.c dispatching, to do with optional array
+    arguments.
 
 0.4.6:
     Added floating-point math feature.
index 4dd3fcdc757d7c36f9e5159b11088bbe766f13c6..af69189b4cf2ef85ec7294d2e965b8b39653821c 100644 (file)
@@ -523,27 +523,27 @@ void execute_loop()
         break;
 
       case op_streamchar:
-        profile_in(2, FALSE);
+        profile_in(0xE0000001, stackptr, FALSE);
         value = inst[0].value & 0xFF;
         (*stream_char_handler)(value);
-        profile_out();
+        profile_out(stackptr);
         break;
       case op_streamunichar:
-        profile_in(2, FALSE);
+        profile_in(0xE0000002, stackptr, FALSE);
         value = inst[0].value;
         (*stream_unichar_handler)(value);
-        profile_out();
+        profile_out(stackptr);
         break;
       case op_streamnum:
-        profile_in(2, FALSE);
+        profile_in(0xE0000003, stackptr, FALSE);
         vals0 = inst[0].value;
         stream_num(vals0, FALSE, 0);
-        profile_out();
+        profile_out(stackptr);
         break;
       case op_streamstr:
-        profile_in(2, FALSE);
+        profile_in(0xE0000004, stackptr, FALSE);
         stream_string(inst[0].value, 0, 0);
-        profile_out();
+        profile_out(stackptr);
         break;
 
       default:
@@ -615,12 +615,12 @@ void execute_loop()
         break;
 
       case op_glk:
-        profile_in(1, FALSE);
+        profile_in(0xF0000000+inst[0].value, stackptr, FALSE);
         value = inst[1].value;
         arglist = pop_arguments(value, 0);
         val0 = perform_glk(inst[0].value, value, arglist);
         store_operand(inst[2].desttype, inst[2].value, val0);
-        profile_out();
+        profile_out(stackptr);
         break;
 
       case op_random:
index c0aac4dccae22643c1e6ec4b91aeb2aa19c98af6..2e03ccdb3551d78e3a3539e0c7799d1547585786 100644 (file)
@@ -23,14 +23,14 @@ void enter_function(glui32 addr, glui32 argc, glui32 *argv)
 
   accelfunc = accel_get_func(addr);
   if (accelfunc) {
-    profile_in(addr, TRUE);
+    profile_in(addr, stackptr, TRUE);
     val = accelfunc(argc, argv);
-    profile_out();
+    profile_out(stackptr);
     pop_callstub(val);
     return;
   }
     
-  profile_in(addr, FALSE);
+  profile_in(addr, stackptr, FALSE);
 
   /* Check the Glulx type identifier byte. */
   functype = Mem1(addr);
@@ -190,8 +190,8 @@ void enter_function(glui32 addr, glui32 argc, glui32 *argv)
 */
 void leave_function()
 {
+  profile_out(stackptr);
   stackptr = frameptr;
-  profile_out();
 }
 
 /* push_callstub():
index 87a8182e713f6ca5d13df12681d5bae1028c853f..7f6418d03364c72900418ac89f2baf77a299c93e 100644 (file)
@@ -15,7 +15,7 @@ glui32 do_gestalt(glui32 val, glui32 val2)
     return 0x00030102; /* Glulx spec version 3.1.2 */
 
   case gestulx_TerpVersion:
-    return 0x00000406; /* Glulxe version 0.4.6 */
+    return 0x00000407; /* Glulxe version 0.4.7 */
 
   case gestulx_ResizeMem:
 #ifdef FIXED_MEMSIZE
index e761cd73f49a31edf672c3f652449f4f1cf6497d..090632abdcbfabf5cb6ba83e6662b094723b34ac 100644 (file)
@@ -120,6 +120,8 @@ typedef struct classtable_struct {
 static int num_classes = 0;
 classtable_t **classes = NULL;
 
+static glui32 find_id_for_stream(strid_t str);
+
 static classtable_t *new_classtable(glui32 firstid);
 static void *classes_get(int classid, glui32 objid);
 static classref_t *classes_put(int classid, void *obj);
@@ -185,6 +187,16 @@ glui32 perform_glk(glui32 funcnum, glui32 numargs, glui32 *arglist)
        directly -- instead of bothering with the whole prototype 
        mess. */
 
+  case 0x0047: /* stream_set_current */
+    if (numargs != 1)
+      goto WrongArgNum;
+    glk_stream_set_current(find_stream_by_id(arglist[0]));
+    break;
+  case 0x0048: /* stream_get_current */
+    if (numargs != 0)
+      goto WrongArgNum;
+    retval = find_id_for_stream(glk_stream_get_current());
+    break;
   case 0x0080: /* put_char */
     if (numargs != 1)
       goto WrongArgNum;
@@ -205,6 +217,16 @@ glui32 perform_glk(glui32 funcnum, glui32 numargs, glui32 *arglist)
       goto WrongArgNum;
     retval = glk_char_to_upper(arglist[0] & 0xFF);
     break;
+  case 0x0128: /* put_char_uni */
+    if (numargs != 1)
+      goto WrongArgNum;
+    glk_put_char_uni(arglist[0]);
+    break;
+  case 0x012B: /* put_char_stream_uni */
+    if (numargs != 2)
+      goto WrongArgNum;
+    glk_put_char_stream_uni(find_stream_by_id(arglist[0]), arglist[1]);
+    break;
 
   WrongArgNum:
     fatal_error("Wrong number of arguments to Glk function.");
@@ -214,7 +236,7 @@ glui32 perform_glk(glui32 funcnum, glui32 numargs, glui32 *arglist)
     /* Go through the full dispatcher prototype foo. */
     char *proto, *cx;
     dispatch_splot_t splot;
-    int argnum;
+    int argnum, argnum2;
 
     /* Grab the string. */
     proto = gidispatch_prototype(funcnum);
@@ -243,9 +265,11 @@ glui32 perform_glk(glui32 funcnum, glui32 numargs, glui32 *arglist)
     gidispatch_call(funcnum, argnum, splot.garglist);
 
     /* Phase 3. */
-    argnum = 0;
+    argnum2 = 0;
     cx = proto;
-    unparse_glk_args(&splot, &cx, 0, &argnum, 0, 0);
+    unparse_glk_args(&splot, &cx, 0, &argnum2, 0, 0);
+    if (argnum != argnum2)
+      fatal_error("Argument counts did not match.");
 
     break;
   }
@@ -605,6 +629,8 @@ static void parse_glk_args(dispatch_splot_t *splot, char **proto, int depth,
       }
       else {
         cx++;
+        if (isarray)
+          ix++;
       }
     }    
   }
@@ -807,6 +833,8 @@ static void unparse_glk_args(dispatch_splot_t *splot, char **proto, int depth,
       }
       else {
         cx++;
+        if (isarray)
+          ix++;
       }
     }    
   }
@@ -838,6 +866,21 @@ strid_t find_stream_by_id(glui32 objid)
   return classes_get(1, objid);
 }
 
+/* find_id_for_stream():
+   The converse of find_stream_by_id(). 
+   This is only needed in this file, so it's static.
+*/
+static glui32 find_id_for_stream(strid_t str)
+{
+  gidispatch_rock_t objrock;
+
+  if (!str)
+    return 0;
+
+  objrock = gidispatch_get_objrock(str, 1);
+  return ((classref_t *)objrock.ptr)->id;
+}
+
 /* Build a hash table to hold a set of Glk objects. */
 static classtable_t *new_classtable(glui32 firstid)
 {
index 72308a650c6bd5510470202cd4d6a245ed536888..64a9f7f4128618472951e40e6132c45605d67bce 100644 (file)
@@ -254,20 +254,21 @@ extern glui32 perform_glk(glui32 funcnum, glui32 numargs, glui32 *arglist);
 extern strid_t find_stream_by_id(glui32 objid);
 
 /* profile.c */
+extern void setup_profile(strid_t stream, char *filename);
 extern int init_profile(void);
 #if VM_PROFILING
 extern glui32 profile_opcount;
 #define profile_tick() (profile_opcount++)
-extern void profile_in(glui32 addr, int accel);
-extern void profile_out(void);
+extern void profile_in(glui32 addr, glui32 stackuse, int accel);
+extern void profile_out(glui32 stackuse);
 extern void profile_fail(char *reason);
 extern void profile_quit(void);
 #else /* VM_PROFILING */
-#define profile_tick()       (0)
-#define profile_in(addr, accel)  (0)
-#define profile_out()        (0)
-#define profile_fail(reason) (0)
-#define profile_quit()       (0)
+#define profile_tick()         (0)
+#define profile_in(addr, stackuse, accel)  (0)
+#define profile_out(stackuse)  (0)
+#define profile_fail(reason)   (0)
+#define profile_quit()         (0)
 #endif /* VM_PROFILING */
 
 /* accel.c */
index ea5bf24afa8e914d604b04b4d3e1ccce49e91fa4..d025fb5c36813d557140b9b8974372da40a19415 100644 (file)
@@ -266,7 +266,7 @@ void heap_free(glui32 addr)
 */
 int heap_get_summary(glui32 *valcount, glui32 **summary)
 {
-  glui32 *arr, len, pos, lx;
+  glui32 *arr, len, pos;
   heapblock_t *blo;
 
   *valcount = 0;
index c7f7345ab26cc118949ffdb4f0d7ccbbb0ebf6fe..ae2b2fb04c046b145ca546166662c583c1b9af71 100644 (file)
@@ -11,29 +11,36 @@ Optionally, this script can also read the debug output of the Inform 6
 compiler (or the assembly output), and use that to figure out the
 names of all the functions that were profiled.
 
+You can also generate profiling output in the same form as dumbfrotz's
+Z-machine profiling output. (If that happens to be what you want.) Use
+the --dumbfrotz argument.
+
 Using this script is currently a nuisance. The requirements:
 
 - You must compile Glulxe with profiling (the VM_PROFILING compile-time
   option).
 - (If you want function names) you should compile your Inform 6 source
   using the -k switch. This generates a "gameinfo.dbg" file.
-- Run Glulxe, play some of the game, and quit. This generates a data
-  file called "profile-raw".
+- Run Glulxe, using the "--profile profile-raw" option. Play some of
+  the game, and quit. This generates a data file called "profile-raw".
 - Run this script, giving gameinfo.dbg and profile-raw as arguments.
+  (You can provide dispatch_dump.xml as an optional third argument.
+  This file gives the names of Glk functions; it is available from
+  https://github.com/erkyrath/glk-dev/tree/master/dispatch_dump .)
 
 To sum up, in command-line form:
 
 % inform -G -k game.inf
-% glulxe game.ulx
-% python profile-analyze.py profile-raw gameinfo.dbg
+% glulxe --profile profile-raw game.ulx
+% python profile-analyze.py profile-raw gameinfo.dbg dispatch_dump.xml
 
 You can also use the assembly output of the Inform compiler, which you
 get with the -a switch. Save the output and use it instead of the debug
 file:
 
 % inform -G -a game.inf > game.asm
-% glulxe game.ulx
-% python profile-analyze.py profile-raw game.asm
+% glulxe --profile profile-raw game.ulx
+% python profile-analyze.py profile-raw game.asm dispatch_dump.xml
 
 The limitations:
 
@@ -50,10 +57,14 @@ you use that data, these will not be named; they will be listed as
 often the most costly ones. (Therefore, you'll almost certainly want
 to use -k.)
 
+If you leave off the "dispatch_dump.xml" argument, everything will
+still work, but @glk function entries will be listed by number rather
+than by name.
+
 You can explore the profiling data in more detail by running the script
 interactively:
 
-% python -i profile-analyze.py profile-raw game.asm
+% python -i profile-analyze.py profile-raw game.asm dispatch_dump.xml
 
 After it runs, you'll be left at a Python prompt. The environment
 will contain mappings called "functions" (mapping addresses to
@@ -89,27 +100,35 @@ A Function object has lots of attributes:
     function).
   self_ops=INT:     The number of opcodes executed during all calls to
     the function, excluding time spent in subcalls.
+  max_depth=INT:    The deepest this function has been nested on the
+    stack, during any call.
+  max_stack_use=INT: The greatest number of words on the stack, during
+    any call. (This is measured when the function returns, so it may
+    not capture the peak stack usage. If a function never returns, e.g.
+    Main__(), then this value is approximate.)
 
 (The self_time is the "cost" used for the original listing.)
 
 Note that if a function does not make any function calls, total_time
 will be the same as self_time (and total_ops the same as self_ops).
 
-Two special function entries may be included. The function with address
-"1" (which is not a legal Glulx function address) represents time spent
-in @glk opcode calls. This will typically have a large self_time, 
-because it includes all the time spent waiting for input.
+Some of the function entries refer to special interpreter operations.
+(These have high addresses, outside the range of normal game files.)
+Functions with addresses in the 0xE0000000 range are the interpreter's
+output opcodes: @streamchar, @streamunichar, @streamnum, @streamstr.
 
-The function with address "2" represents the time spent printing string
-data (the @streamchar, @streamunichar, @streamnum, and @streamstr
-opcodes).
+Functions with addresses in the 0xF0000000 range are @glk opcode calls.
+The number in the lower bits specifies which Glk function was called.
+You will always see a large self_time for function 0xF00000C0; this
+represents all the time spent waiting for input in glk_select().
 
-(Both "1" and "2" represent time spent in the Glk library, but they
-get there by different code paths.)
+(Both the 0xE0000000 and 0xF0000000 entries represent time spent in the
+Glk library, but they get there by different code paths.)
 
-The function with the lowest address (ignoring "1" and "2") is the
-top-level Main__() function generated by the compiler. Its total_time
-is the running time of the entire program.
+The function with the lowest address is the top-level Main__()
+function generated by the compiler. Its total_time is the running time
+of the entire program; its total_ops is the number of opcodes executed
+by the entire program; its max_depth is zero.
 
 """
 
@@ -117,8 +136,14 @@ import sys, os.path
 import xml.sax
 from struct import unpack
 
+dumb_frotz_mode = False
+
+if ('--dumbfrotz' in sys.argv):
+    sys.argv.remove('--dumbfrotz')
+    dumb_frotz_mode = True
+
 if (len(sys.argv) < 2):
-    print "Usage: profile-analyze.py profile-raw [ gameinfo.dbg | game.asm ]"
+    print "Usage: profile-analyze.py [--dumbfrotz] profile-raw [ gameinfo.dbg | game.asm ] [ dispatch_dump.xml ]"
     sys.exit(1)
 
 profile_raw = sys.argv[1]
@@ -133,10 +158,21 @@ if (len(sys.argv) >= 3):
         print 'File not readable:', game_asm
         sys.exit(1)
 
+dispatch_dump = None
+if (len(sys.argv) >= 4):
+    dispatch_dump = sys.argv[3]
+    if (not os.path.exists(dispatch_dump)):
+        print 'File not readable:', dispatch_dump
+        sys.exit(1)
+
 special_functions = {
-    1: 'glk', 2: 'streamout'
+    0xE0000001: 'streamchar',
+    0xE0000002: 'streamunichar',
+    0xE0000003: 'streamnum',
+    0xE0000004: 'streamstr',
 }
-max_special_functions = max(special_functions.keys())
+
+glk_functions = {}
 
 functions = None
 sourcemap = None
@@ -146,7 +182,14 @@ class Function:
         self.addr = addr
         self.hexaddr = hexaddr
         val = special_functions.get(addr)
-        if (val is None):
+        if (addr >= 0xF0000000):
+            name = glk_functions.get(addr-0xF0000000)
+            if (not name):
+                name = hex(addr-0xF0000000)[2:]
+                name = '$' + name.replace('L', '')
+            self.name = '<@glk_' + name + '>'
+            self.special = True
+        elif (val is None):
             self.name = '<???>'
             self.special = False
         else:
@@ -161,6 +204,10 @@ class Function:
         self.total_time = float(attrs['total_time'])
         self.self_ops   =   int(attrs['self_ops'])
         self.self_time  = float(attrs['self_time'])
+        if (attrs.has_key('max_depth')):
+            self.max_depth     = int(attrs['max_depth'])
+        if (attrs.has_key('max_stack_use')):
+            self.max_stack_use = int(attrs['max_stack_use'])
         
     def __repr__(self):
         return '<Function $' + self.hexaddr + ' ' + repr(self.name) + '>'
@@ -174,6 +221,23 @@ class Function:
         print '  %.6f sec (%d ops) spent executing' % (self.self_time, self.self_ops)
         print '  %.6f sec (%d ops) including child calls' % (self.total_time, self.total_ops)
 
+    def dump_dumbfrotz_style(self):
+        percent1 = '    '
+        percent2 = '    '
+        pc1 = int(100*(float(self.self_ops)/float(ops_executed)))
+        pc2 = int(100*(float(self.total_ops)/float(ops_executed)))
+        if (pc1 > 0):
+            percent1 = "%3d%%" % pc1
+        if (pc2 > 0):
+            percent2 = "%3d%%" % pc2
+        print '%-36s %s %-10lu %s %-10lu %-10lu %-4d' % (self.name, percent1, self.self_ops, percent2, self.total_ops, self.call_count, self.max_depth)
+
+class DispatchDumpHandler(xml.sax.handler.ContentHandler):
+    def startElement(self, name, attrs):
+        if (name == 'function'):
+            addr = int(attrs['id'])
+            glk_functions[addr] = str(attrs['name'])
+        
 class ProfileRawHandler(xml.sax.handler.ContentHandler):
     def startElement(self, name, attrs):
         global functions
@@ -304,7 +368,7 @@ class DebugFile:
         dat = fl.read(1)
         num = unpack('>B', dat)[0]
         name = self.read_string(fl)
-        self.arrays[num] = name
+        self.globals[num] = name
     
     def read_array_rec(self, fl):
         dat = fl.read(2)
@@ -416,19 +480,25 @@ class DebugFile:
                         
 # Begin the work
             
+if (dispatch_dump):
+    xml.sax.parse(dispatch_dump, DispatchDumpHandler())
+        
 xml.sax.parse(profile_raw, ProfileRawHandler())
 
 source_start = min([ func.addr for func in functions.values()
     if not func.special ])
-print 'Code segment begins at', hex(source_start)
 
-print len(functions), 'called functions found in', profile_raw
+if (not dumb_frotz_mode):
+    print 'Code segment begins at', hex(source_start)
+    print len(functions), 'called functions found in', profile_raw
 
 if (game_asm):
     fl = open(game_asm, 'rb')
     val = fl.read(2)
     fl.close()
-    if (val == '\xde\xbf'):
+    if (not val):
+        pass
+    elif (val == '\xde\xbf'):
         fl = open(game_asm, 'rb')
         debugfile = DebugFile(fl)
         fl.close()
@@ -454,8 +524,9 @@ if (sourcemap):
         func.name = funcname
         func.linenum = linenum
     
-    if (badls):
-        print len(badls), 'functions from', profile_raw, 'did not appear in asm (veneer functions)'
+    if (not dumb_frotz_mode):
+        if (badls):
+            print len(badls), 'functions from', profile_raw, 'did not appear in asm (veneer functions)'
     
     function_names = {}
     for func in functions.values():
@@ -463,11 +534,29 @@ if (sourcemap):
 
 if (sourcemap):
     uncalled_funcs = [ funcname for (addr, (linenum, funcname)) in sourcemap.items() if (addr+source_start) not in functions ]
-    print len(uncalled_funcs), 'functions found in', game_asm, 'were never called'
-
-print 'Functions that consumed the most time (excluding children):'
-ls = functions.values()
-ls.sort(lambda x1, x2: cmp(x2.self_time, x1.self_time))
-for func in ls[:10]:
-    func.dump()
-
+    if (not dumb_frotz_mode):
+        print len(uncalled_funcs), 'functions found in', game_asm, 'were never called'
+
+if (dumb_frotz_mode):
+    ls = functions.values()
+    ls.sort(lambda x1, x2: cmp(x2.total_ops, x1.total_ops))
+    ops_executed = 0
+    routine_calls = 0
+    max_stack_use = max([func.max_stack_use for func in ls])
+    for func in ls:
+        if (func.total_ops > ops_executed):
+            ops_executed = func.total_ops
+        routine_calls = routine_calls + func.call_count
+    print 'Total opcodes: %lu' % ops_executed
+    print 'Total routine calls: %lu' % routine_calls
+    print 'Max. stack usage: %li' % max_stack_use
+    print ''
+    print '%-35s      %-10s      %-10s %-10s %-4s' % ('Routine', 'Ops', 'Ops(+Subs)', 'Calls', 'Nest')
+    for func in ls:
+        func.dump_dumbfrotz_style()
+else:
+    print 'Functions that consumed the most time (excluding children):'
+    ls = functions.values()
+    ls.sort(lambda x1, x2: cmp(x2.self_time, x1.self_time))
+    for func in ls[:10]:
+        func.dump()
index e7a0810968812b240b2c99c5e755c8651d277d78..c8716221fb0e54608b1cc0cbebf69b0a9b4d63c1 100644 (file)
@@ -12,8 +12,11 @@ call stack. In fact, it's downright stupid. @restart, @restore,
 @restoreundo, or @throw will kill the interpreter.
 
 On a normal VM exit (end of top-level routine or @quit), the profiler
-writes out a data file called "profile-raw". This is an XML file of
-the form
+writes out a data file, using the filename you provided with the 
+"--profile" option. Note that if the VM exits via glk_exit(), or is
+interrupted, the data file will be created (empty) but never filled in.
+
+The data file is an XML file of the form
 
 <profile>
   <function ... />
@@ -37,25 +40,33 @@ program's run. Each function tag includes the following attributes:
     function).
   self_ops=INT:     The number of opcodes executed during all calls to
     the function, excluding time spent in subcalls.
+  max_depth=INT:    The deepest this function has been nested on the
+    stack, during any call.
+  max_stack_use=INT: The greatest number of words on the stack, during
+    any call. (This is measured when the function returns, so it may
+    not capture the peak stack usage. If a function never returns, e.g.
+    Main__(), then this value is approximate.)
 
 Note that if a function does not make any function calls, total_time
 will be the same as self_time (and total_ops the same as self_ops).
 
-Two special function entries may be included. The function with address
-"1" (which is not a legal Glulx function address) represents time spent
-in @glk opcode calls. This will typically have a large self_time, 
-because it includes all the time spent waiting for input.
+Some of the function entries refer to special interpreter operations.
+(These have high addresses, outside the range of normal game files.)
+Functions with addresses in the 0xE0000000 range are the interpreter's
+output opcodes: @streamchar, @streamunichar, @streamnum, @streamstr.
 
-The function with address "2" represents the time spent printing string
-data (the @streamchar, @streamunichar, @streamnum, and @streamstr
-opcodes).
+Functions with addresses in the 0xF0000000 range are @glk opcode calls.
+The number in the lower bits specifies which Glk function was called.
+You will always see a large self_time for function 0xF00000C0; this
+represents all the time spent waiting for input in glk_select().
 
-(Both "1" and "2" represent time spent in the Glk library, but they
-get there by different code paths.)
+(Both the 0xE0000000 and 0xF0000000 entries represent time spent in the
+Glk library, but they get there by different code paths.)
 
-The function with the lowest address (ignoring "1" and "2") is the
-top-level Main__() function generated by the compiler. Its total_time
-is the running time of the entire program.
+The function with the lowest address is the top-level Main__()
+function generated by the compiler. Its total_time is the running time
+of the entire program; its total_ops is the number of opcodes executed
+by the entire program; its max_depth is zero.
 
  */
 
@@ -68,6 +79,11 @@ is the running time of the entire program.
 #include <string.h>
 #include <sys/time.h>
 
+/* Set if the --profile switch is used. */
+static int profiling_active = FALSE;
+static char *profiling_filename = NULL;
+static strid_t profiling_stream = NULL;
+
 typedef struct function_struct {
   glui32 addr;
 
@@ -76,6 +92,8 @@ typedef struct function_struct {
   glui32 entry_depth;
   struct timeval entry_start_time;
   glui32 entry_start_op;
+  glui32 max_depth;
+  glui32 max_stack_use;
   struct timeval total_time;
   glui32 total_ops;
   struct timeval self_time;
@@ -87,6 +105,7 @@ typedef struct function_struct {
 typedef struct frame_struct {
   struct frame_struct *parent;
   function_t *func;
+  glui32 depth;
 
   struct timeval entry_time;
   glui32 entry_op;
@@ -100,12 +119,49 @@ typedef struct frame_struct {
 static function_t **functions = NULL;
 static frame_t *current_frame = NULL;
 
+/* This counter is globally visible, because the profile_tick() macro
+   increments it. */
 glui32 profile_opcount = 0;
 
+/* This is called from the setup code -- glkunix_startup_code(), for the
+   Unix version. If called, the interpreter will keep profiling information,
+   and write it out at shutdown time. If this is not called, the interpreter
+   will skip all the profiling code. (Although it won't be quite as fast
+   as if the VM_PROFILING symbol were compiled out entirely.)
+
+   The arguments are a little tricky, because I developed this on Unix,
+   but I want it to remain accessible on all platforms. Pass a writable
+   stream object as the first argument; at game-shutdown time, the terp
+   will write the profiling data to this object and then close it.
+
+   However, if it's not convenient to open a stream in the startup code,
+   you can simply pass a filename as the second argument. This filename
+   will be opened according to the usual Glk data file rules, which means
+   it may wind up in a sandboxed data directory. The filename should not
+   contain slashes or other pathname separators.
+
+   If you pass NULL for both arguments, a file called "profile-raw" will
+   be written.
+*/
+void setup_profile(strid_t stream, char *filename)
+{
+  profiling_active = TRUE;
+
+  if (stream)
+    profiling_stream = stream;
+  else if (filename)
+    profiling_filename = filename;
+  else
+    profiling_filename = "profile-raw";
+}
+
 int init_profile()
 {
   int bucknum;
 
+  if (!profiling_active)
+    return TRUE;
+
   functions = (function_t **)glulx_malloc(FUNC_HASH_SIZE
     * sizeof(function_t *));
   if (!functions) 
@@ -143,6 +199,8 @@ static function_t *get_function(glui32 addr)
     func->total_ops = 0;
     timerclear(&func->self_time);
     func->self_ops = 0;
+    func->max_depth = 0;
+    func->max_stack_use = 0;
   }
 
   return func;
@@ -154,12 +212,15 @@ static char *timeprint(struct timeval *tv, char *buf)
   return buf;
 }
 
-void profile_in(glui32 addr, int accel)
+void profile_in(glui32 addr, glui32 stackuse, int accel)
 {
   frame_t *fra;
   function_t *func;
   struct timeval now;
 
+  if (!profiling_active)
+    return;
+
   /* printf("### IN: %lx%s\n", addr, (accel?" accel":"")); */
 
   gettimeofday(&now, NULL);
@@ -174,6 +235,9 @@ void profile_in(glui32 addr, int accel)
   }
   func->entry_depth += 1;
 
+  if (func->max_stack_use < stackuse)
+    func->max_stack_use = stackuse;
+
   fra = (frame_t *)glulx_malloc(sizeof(frame_t));
   if (!fra)
     fatal_error("Profiler: cannot malloc frame.");
@@ -182,6 +246,8 @@ void profile_in(glui32 addr, int accel)
   fra->parent = current_frame;
   current_frame = fra;
 
+  if (fra->parent)
+    fra->depth = fra->parent->depth + 1;
   fra->func = func;
   fra->entry_time = now;
   fra->entry_op = profile_opcount;
@@ -189,13 +255,16 @@ void profile_in(glui32 addr, int accel)
   fra->children_ops = 0;
 }
 
-void profile_out()
+void profile_out(glui32 stackuse)
 {
   frame_t *fra;
   function_t *func;
   struct timeval now, runtime;
   glui32 runops;
 
+  if (!profiling_active)
+    return;
+
   /* printf("### OUT\n"); */
 
   if (!current_frame) 
@@ -214,6 +283,11 @@ void profile_out()
   func->self_ops += runops;
   func->self_ops -= fra->children_ops;
 
+  if (func->max_depth < fra->depth)
+    func->max_depth = fra->depth;
+  if (func->max_stack_use < stackuse)
+    func->max_stack_use = stackuse;
+
   if (fra->parent) {
     timeradd(&runtime, &fra->parent->children_time, &fra->parent->children_time);
     fra->parent->children_ops += runops;
@@ -245,6 +319,9 @@ void profile_out()
 
 void profile_fail(char *reason)
 {
+  if (!profiling_active)
+    return;
+
   fatal_error_2("Profiler: unable to handle operation", reason);
 }
 
@@ -253,18 +330,28 @@ void profile_quit()
   int bucknum;
   function_t *func;
   char linebuf[512];
-  frefid_t profref;
   strid_t profstr;
 
+  if (!profiling_active)
+    return;
+
   while (current_frame) {
-    profile_out();
+    profile_out(0);
   }
 
-  profref = glk_fileref_create_by_name(fileusage_BinaryMode|fileusage_Data, "profile-raw", 0);
-  if (!profref)
-    fatal_error("Profiler: unable to create profile-raw file");
+  if (profiling_stream) {
+    profstr = profiling_stream;
+  }
+  else if (profiling_filename) {
+    frefid_t profref = glk_fileref_create_by_name(fileusage_BinaryMode|fileusage_Data, profiling_filename, 0);
+    if (!profref)
+        fatal_error_2("Profiler: unable to create profile output fileref", profiling_filename);
 
-  profstr = glk_stream_open_file(profref, filemode_Write, 0);
+    profstr = glk_stream_open_file(profref, filemode_Write, 0);
+  }
+  else {
+    fatal_error("Profiler: no profile output handle!");
+  }
 
   glk_put_string_stream(profstr, "<profile>\n");
 
@@ -280,12 +367,13 @@ void profile_quit()
         func->self_ops,
         timeprint(&func->self_time, self_buf));
       ### */
-      sprintf(linebuf, "  <function addr=\"%lx\" call_count=\"%ld\" accel_count=\"%ld\" total_ops=\"%ld\" total_time=\"%s\" self_ops=\"%ld\" self_time=\"%s\" />\n",
-        func->addr, func->call_count, func->accel_count,
-        func->total_ops,
+      sprintf(linebuf, "  <function addr=\"%lx\" call_count=\"%ld\" accel_count=\"%ld\" total_ops=\"%ld\" total_time=\"%s\" self_ops=\"%ld\" self_time=\"%s\" max_depth=\"%ld\" max_stack_use=\"%ld\" />\n",
+        (unsigned long)func->addr, (long)func->call_count, (long)func->accel_count,
+        (long)func->total_ops,
         timeprint(&func->total_time, total_buf),
-        func->self_ops,
-        timeprint(&func->self_time, self_buf));
+        (long)func->self_ops,
+        timeprint(&func->self_time, self_buf),
+        (long)func->max_depth, (long)func->max_stack_use);
       glk_put_string_stream(profstr, linebuf);
     }
   }
@@ -300,6 +388,11 @@ void profile_quit()
 
 #else /* VM_PROFILING */
 
+void setup_profile(strid_t stream, char *filename)
+{
+    /* Profiling is not compiled in. Do nothing. */
+}
+
 int init_profile()
 {
     /* Profiling is not compiled in. Do nothing. */
index 26bf480120ee6f697226f27a34ab8bbd2832fd98..f93dcd409fc36de77d6b5823a34373340c41e379 100644 (file)
@@ -5,7 +5,6 @@
 
 #include "glk.h"
 #include "glulxe.h"
-#include "opcodes.h"
 
 #define serop_KeyIndirect (0x01)
 #define serop_ZeroKeyTerminates (0x02)
index a547652a688a181c85f77a3f6a9972cb142ece34..036cf88b33b4b5f4f99bcf917340db3dba994d6a 100644 (file)
@@ -35,9 +35,6 @@ static int undo_chain_size = 0;
 static int undo_chain_num = 0;
 unsigned char **undo_chain = NULL;
 
-static glui32 protect_pos = 0;
-static glui32 protect_len = 0;
-
 static glui32 write_memstate(dest_t *dest);
 static glui32 write_heapstate(dest_t *dest, int portable);
 static glui32 write_stackstate(dest_t *dest, int portable);
@@ -810,9 +807,8 @@ static glui32 read_heapstate(dest_t *dest, glui32 chunklen, int portable,
 
 static glui32 write_stackstate(dest_t *dest, int portable)
 {
-  glui32 res, pos;
-  glui32 val, lx;
-  unsigned char ch;
+  glui32 res;
+  glui32 lx;
   glui32 lastframe;
 
   /* If we're storing for the purpose of undo, we don't need to do any
@@ -970,8 +966,7 @@ static glui32 write_stackstate(dest_t *dest, int portable)
 
 static glui32 read_stackstate(dest_t *dest, glui32 chunklen, int portable)
 {
-  glui32 res, pos;
-  unsigned char ch;
+  glui32 res;
   glui32 frameend, frm, frm2, frm3, locpos, frlen, numlocals;
 
   if (chunklen > stacksize)
index b3f119dc3dcaa2d9d0e5dda4c3f345682ffe52db..f33d311e415f71d2e115e56a42e8752a160ecaaa 100644 (file)
@@ -820,7 +820,7 @@ char *make_temp_string(glui32 addr)
 {
   int ix, len;
   glui32 addr2;
-  char *res, *cx;
+  char *res;
 
   if (Mem1(addr) != 0xE0)
     fatal_error("String argument to a Glk call must be unencoded.");
@@ -849,7 +849,7 @@ glui32 *make_temp_ustring(glui32 addr)
 {
   int ix, len;
   glui32 addr2;
-  glui32 *res, *cx;
+  glui32 *res;
 
   if (Mem1(addr) != 0xE2)
     fatal_error("Ustring argument to a Glk call must be unencoded.");
index 2e62f22eace4e9d0e1182c796a432b0bf19de615..38779a2d788443d4e8eb1bed9c4cfbad0ea201d5 100644 (file)
@@ -3,14 +3,22 @@
     http://eblong.com/zarf/glulx/index.html
 */
 
+#include <string.h>
 #include "glk.h"
 #include "glulxe.h"
 #include "glkstart.h" /* This comes with the Glk library. */
-#include <string.h>
 
-/* The only command-line argument is the filename. */
+/* The only command-line argument is the filename. And the profiling switch,
+   if that's compiled in. The only *two* command-line arguments are... 
+*/
 glkunix_argumentlist_t glkunix_arguments[] = {
+
+#if VM_PROFILING
+  { "--profile", glkunix_arg_ValueFollows, "Generate profiling information to a file." },
+#endif /* VM_PROFILING */
+
   { "", glkunix_arg_ValueFollows, "filename: The game file to load." },
+
   { NULL, glkunix_arg_End, NULL }
 };
 
@@ -18,35 +26,64 @@ int glkunix_startup_code(glkunix_startup_t *data)
 {
   /* It turns out to be more convenient if we return TRUE from here, even 
      when an error occurs, and display an error in glk_main(). */
-  char *cx;
+  int ix;
+  char *filename = NULL;
   unsigned char buf[12];
   int res;
 
 #ifdef GARGLK
-  garglk_set_program_name("Glulxe 0.4.6");
-  garglk_set_program_info("Glulxe 0.4.6 by Andrew Plotkin");
+  char *cx;
+  garglk_set_program_name("Glulxe 0.4.7");
+  garglk_set_program_info("Glulxe 0.4.7 by Andrew Plotkin");
 #endif
 
-  if (data->argc <= 1) {
+  /* Parse out the arguments. They've already been checked for validity,
+     and the library-specific ones stripped out.
+     As usual for Unix, the zeroth argument is the executable name. */
+  for (ix=1; ix<data->argc; ix++) {
+
+#if VM_PROFILING
+    if (!strcmp(data->argv[ix], "--profile")) {
+      ix++;
+      if (ix<data->argc) {
+        strid_t profstr = glkunix_stream_open_pathname_gen(data->argv[ix], TRUE, FALSE, 1);
+        if (!profstr) {
+          init_err = "Unable to open profile output file.";
+          init_err2 = data->argv[ix];
+          return TRUE;
+        }
+        setup_profile(profstr, NULL);
+      }
+      continue;
+    }
+#endif /* VM_PROFILING */
+
+    if (filename) {
+      init_err = "You must supply exactly one game file.";
+      return TRUE;
+    }
+    filename = data->argv[ix];
+  }
+
+  if (!filename) {
     init_err = "You must supply the name of a game file.";
 #ifdef GARGLK
     return TRUE; /* Hack! but I want error message in glk window */
 #endif
        return FALSE;
   }
-  cx = data->argv[1];
     
-  gamefile = glkunix_stream_open_pathname(cx, FALSE, 1);
+  gamefile = glkunix_stream_open_pathname(filename, FALSE, 1);
   if (!gamefile) {
     init_err = "The game file could not be opened.";
-    init_err2 = cx;
+    init_err2 = filename;
     return TRUE;
   }
 
 #ifdef GARGLK
-  cx = strrchr(data->argv[1], '/');
-  if (!cx) cx = strrchr(data->argv[1], '\\');
-  garglk_set_story_name(cx ? cx + 1 : data->argv[1]);
+  cx = strrchr(filename, '/');
+  if (!cx) cx = strrchr(filename, '\\');
+  garglk_set_story_name(cx ? cx + 1 : filename);
 #endif
 
   /* Now we have to check to see if it's a Blorb file. */
index 2496948200c121a06fc1f64141d04d1625c29450..96d861d7e6ad7909527dba071c3fe32519d64f98 100644 (file)
@@ -42,7 +42,7 @@ libchimara_la_SOURCES = \
        style.c style.h \
        timer.c timer.h \
        window.c window.h
-libchimara_la_CPPFLAGS = \
+libchimara_la_CPPFLAGS = $(AM_CPPFLAGS) \
        -DG_LOG_DOMAIN=\"Chimara\" \
        -DLOCALEDIR=\""$(datadir)/locale"\" \
        -DPLUGINDIR=\""$(pluginpath)"\" \
@@ -105,9 +105,14 @@ CLEANFILES += $(gir_DATA) $(typelib_DATA)
 endif
 GITIGNOREFILES = Chimara-1.0.gir Chimara-1.0.typelib
 
-# Currently, install the Vala VAPI file statically - generation is broken?
-
 vapidir = $(datadir)/vala/vapi
 dist_vapi_DATA = chimara.vapi
 
+if BUILDING_VAPI
+chimara.vapi: $(INTROSPECTION_GIRS)
+       $(AM_V_GEN)$(VAPIGEN) --library=chimara --pkg gtk+-2.0 Chimara-1.0.gir && \
+       touch $@
+endif
+
+MAINTAINERCLEANFILES = chimara.vapi
 -include $(top_srcdir)/git.mk
index b7c0a7d6f3df2f9290ed5e459ea02d9c481b9e26..6a4c0d3e07a402aec0ee11ce1132ca1a3912dffa 100644 (file)
@@ -148,7 +148,8 @@ enum {
        PROP_SPACING,
        PROP_PROGRAM_NAME,
        PROP_PROGRAM_INFO,
-       PROP_STORY_NAME
+       PROP_STORY_NAME,
+       PROP_RUNNING
 };
 
 enum {
@@ -170,6 +171,8 @@ G_DEFINE_TYPE(ChimaraGlk, chimara_glk, GTK_TYPE_CONTAINER);
 static void
 chimara_glk_init(ChimaraGlk *self)
 {
+       chimara_init(); /* This is a library entry point */
+
     gtk_widget_set_has_window(GTK_WIDGET(self), FALSE);
 
     ChimaraGlkPrivate *priv = CHIMARA_GLK_PRIVATE(self);
@@ -267,6 +270,9 @@ chimara_glk_get_property(GObject *object, guint prop_id, GValue *value, GParamSp
                case PROP_STORY_NAME:
                        g_value_set_string(value, priv->story_name);
                        break;
+               case PROP_RUNNING:
+                       g_value_set_boolean(value, priv->running);
+                       break;
                default:
             G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
     }
@@ -947,6 +953,17 @@ chimara_glk_class_init(ChimaraGlkClass *klass)
                NULL,
                G_PARAM_READABLE | G_PARAM_STATIC_STRINGS) );
        
+       /**
+        * ChimaraGlk:running:
+        *
+        * Whether this Glk widget is currently running a game or not.
+        */
+       g_object_class_install_property(object_class, PROP_RUNNING,
+               g_param_spec_boolean("running", _("Running"),
+               _("Whether there is a program currently running"),
+               FALSE,
+               G_PARAM_READABLE | G_PARAM_STATIC_STRINGS) );
+
        /* Private data */
     g_type_class_add_private(klass, sizeof(ChimaraGlkPrivate));
 }
index b3ea8b86002db3d8bfcaac774929217eaf3898db..8001adafbfdb3f40ccc0ce4101bb8fc971e8ba12 100644 (file)
@@ -21,13 +21,13 @@ G_BEGIN_DECLS
  * 
  * This structure contains no public members.
  */
-typedef struct _ChimaraGlk {
+typedef struct {
        GtkContainer parent_instance;
     
        /*< public >*/
 } ChimaraGlk;
 
-typedef struct _ChimaraGlkClass {
+typedef struct {
        GtkContainerClass parent_class;
        /* Signals */
        void(* stopped) (ChimaraGlk *self);
index ed132bb5f01d32e0e1c3080b7166e77440967de1..95f8b93dca03082d9fe9fd69d4dca18a4da6f279 100644 (file)
@@ -159,6 +159,8 @@ chimara_if_text_buffer_output(ChimaraGlk *glk, guint32 win_rock, gchar *output)
 static void
 chimara_if_init(ChimaraIF *self)
 {
+       chimara_init(); /* This is a library entry point */
+
        CHIMARA_IF_USE_PRIVATE(self, priv);
        priv->preferred_interpreter[CHIMARA_IF_FORMAT_Z5] = CHIMARA_IF_INTERPRETER_FROTZ;
        priv->preferred_interpreter[CHIMARA_IF_FORMAT_Z6] = CHIMARA_IF_INTERPRETER_NITFOL;
index 3bbe06ac7e0b2fd16d10976a6adccd9b3b47151a..72d64610346542555c9b68407b1d04f39cb833ea 100644 (file)
@@ -24,7 +24,7 @@ G_BEGIN_DECLS
  *
  * Constants representing all game formats supported by the Chimara system.
  */
-typedef enum _ChimaraIFFormat {
+typedef enum {
        /*< private >*/
        CHIMARA_IF_FORMAT_NONE = -1,
        /*< public >*/
@@ -47,7 +47,7 @@ typedef enum _ChimaraIFFormat {
  * 
  * Constants representing the available interpreter plugins.
  */
-typedef enum _ChimaraIFInterpreter {
+typedef enum {
     /*< private >*/
        CHIMARA_IF_INTERPRETER_NONE = -1,
        /*< public >*/
@@ -78,7 +78,7 @@ typedef enum _ChimaraIFInterpreter {
  * Allowed values for the #ChimaraIF:interpreter-number property. All trademarks
  * are the property of their respective owners.
  */
-typedef enum _ChimaraIFZmachineVersion {
+typedef enum {
        CHIMARA_IF_ZMACHINE_DEFAULT = 0,
        CHIMARA_IF_ZMACHINE_DECSYSTEM_20,
        CHIMARA_IF_ZMACHINE_APPLE_IIE,
@@ -100,13 +100,13 @@ typedef enum _ChimaraIFZmachineVersion {
  * 
  * This structure contains no public members.
  */
-typedef struct _ChimaraIF {
+typedef struct {
        ChimaraGlk parent_instance;
        
        /*< public >*/
 } ChimaraIF;
 
-typedef struct _ChimaraIFClass {
+typedef struct {
        ChimaraGlkClass parent_class;
        /* Signals */
        void(* command) (ChimaraIF *self, gchar *input, gchar *response);
diff --git a/libchimara/chimara.vapi b/libchimara/chimara.vapi
deleted file mode 100644 (file)
index 7b014d8..0000000
+++ /dev/null
@@ -1,138 +0,0 @@
-/* chimara.vapi generated by vapigen, do not modify. */
-
-[CCode (cprefix = "Chimara", lower_case_cprefix = "chimara_", gir_namespace = "Chimara", gir_version = "1.0")]
-namespace Chimara {
-       [CCode (cheader_filename = "libchimara/chimara-glk.h,libchimara/chimara-if.h")]
-       public class Glk : Gtk.Container, Atk.Implementor, Gtk.Buildable {
-               [CCode (type = "GtkWidget*", has_construct_function = false)]
-               public Glk ();
-               public void feed_char_input (uint32 keyval);
-               public void feed_line_input (string text);
-               public bool get_interactive ();
-               public bool get_protect ();
-               public bool get_running ();
-               public uint get_spacing ();
-               public unowned Gtk.TextTag get_tag (Chimara.GlkWindowType window, string name);
-               public unowned string[] get_tag_names (out uint num_tags);
-               public bool is_char_input_pending ();
-               public bool is_line_input_pending ();
-               public bool run (string plugin, int argc, string argv) throws GLib.Error;
-               public bool set_css_from_file (string filename) throws GLib.Error;
-               public void set_css_from_string (string css);
-               public void set_css_to_default ();
-               public void set_interactive (bool interactive);
-               public void set_protect (bool protect);
-               public void set_resource_load_callback (owned Chimara.ResourceLoadFunc func);
-               public void set_spacing (uint spacing);
-               public void stop ();
-               public void update_style ();
-               public void wait ();
-               public bool interactive { get; set construct; }
-               [NoAccessorMethod]
-               public string program_info { get; }
-               [NoAccessorMethod]
-               public string program_name { get; }
-               public bool protect { get; set construct; }
-               public uint spacing { get; set construct; }
-               [NoAccessorMethod]
-               public string story_name { get; }
-               public signal void char_input (uint object, uint p0);
-               public signal void iliad_screen_update (bool object);
-               public signal void line_input (uint object, string p0);
-               public signal void started ();
-               public signal void stopped ();
-               public signal void text_buffer_output (uint object, string p0);
-               public signal void waiting ();
-       }
-       [CCode (cheader_filename = "libchimara/chimara-glk.h,libchimara/chimara-if.h")]
-       public class IF : Chimara.Glk, Atk.Implementor, Gtk.Buildable {
-               [CCode (type = "GtkWidget*", has_construct_function = false)]
-               public IF ();
-               public Chimara.IFFormat get_format ();
-               public Chimara.IFInterpreter get_interpreter ();
-               public Chimara.IFInterpreter get_preferred_interpreter (Chimara.IFFormat format);
-               public bool run_game (string gamefile) throws GLib.Error;
-               public void set_preferred_interpreter (Chimara.IFFormat format, Chimara.IFInterpreter interpreter);
-               [NoAccessorMethod]
-               public bool expand_abbreviations { get; set construct; }
-               [NoAccessorMethod]
-               public string graphics_file { get; set construct; }
-               [NoAccessorMethod]
-               public bool ignore_errors { get; set construct; }
-               [NoAccessorMethod]
-               public uint interpreter_number { get; set construct; }
-               [NoAccessorMethod]
-               public bool piracy_mode { get; set construct; }
-               [NoAccessorMethod]
-               public int random_seed { get; set; }
-               [NoAccessorMethod]
-               public bool random_seed_set { get; set construct; }
-               [NoAccessorMethod]
-               public bool tandy_bit { get; set construct; }
-               [NoAccessorMethod]
-               public bool typo_correction { get; set construct; }
-               public signal void command (string object, string p0);
-       }
-       [CCode (cprefix = "CHIMARA_GLK_TEXT_", cheader_filename = "libchimara/chimara-glk.h,libchimara/chimara-if.h")]
-       public enum GlkWindowType {
-               BUFFER,
-               GRID
-       }
-       [CCode (cprefix = "CHIMARA_IF_FORMAT_", cheader_filename = "libchimara/chimara-glk.h,libchimara/chimara-if.h")]
-       public enum IFFormat {
-               [CCode (cname = "CHIMARA_IF_FORMAT_Z5")]
-               FORMAT_Z5,
-               [CCode (cname = "CHIMARA_IF_FORMAT_Z6")]
-               FORMAT_Z6,
-               [CCode (cname = "CHIMARA_IF_FORMAT_Z8")]
-               FORMAT_Z8,
-               [CCode (cname = "CHIMARA_IF_FORMAT_Z_BLORB")]
-               FORMAT_Z_BLORB,
-               [CCode (cname = "CHIMARA_IF_FORMAT_GLULX")]
-               FORMAT_GLULX,
-               [CCode (cname = "CHIMARA_IF_FORMAT_GLULX_BLORB")]
-               FORMAT_GLULX_BLORB
-       }
-       [CCode (cprefix = "CHIMARA_IF_INTERPRETER_", cheader_filename = "libchimara/chimara-glk.h,libchimara/chimara-if.h")]
-       public enum IFInterpreter {
-               [CCode (cname = "CHIMARA_IF_INTERPRETER_FROTZ")]
-               INTERPRETER_FROTZ,
-               [CCode (cname = "CHIMARA_IF_INTERPRETER_NITFOL")]
-               INTERPRETER_NITFOL,
-               [CCode (cname = "CHIMARA_IF_INTERPRETER_GLULXE")]
-               INTERPRETER_GLULXE,
-               [CCode (cname = "CHIMARA_IF_INTERPRETER_GIT")]
-               INTERPRETER_GIT
-       }
-       [CCode (cprefix = "CHIMARA_IF_ZMACHINE_", cheader_filename = "libchimara/chimara-glk.h,libchimara/chimara-if.h")]
-       public enum IFZmachineVersion {
-               DEFAULT,
-               DECSYSTEM_20,
-               APPLE_IIE,
-               MACINTOSH,
-               AMIGA,
-               ATARI_ST,
-               IBM_PC,
-               COMMODORE_128,
-               COMMODORE_64,
-               APPLE_IIC,
-               APPLE_IIGS,
-               TANDY_COLOR
-       }
-       [CCode (cprefix = "CHIMARA_RESOURCE_", cheader_filename = "libchimara/chimara-glk.h,libchimara/chimara-if.h")]
-       public enum ResourceType {
-               SOUND,
-               IMAGE
-       }
-       [CCode (cprefix = "ERROR_", cheader_filename = "libchimara/chimara-glk.h,libchimara/chimara-if.h")]
-       public errordomain Error {
-               LOAD_MODULE_ERROR,
-               NO_GLK_MAIN,
-               PLUGIN_NOT_FOUND,
-               PLUGIN_ALREADY_RUNNING,
-       }
-       [CCode (cheader_filename = "libchimara/chimara-glk.h,libchimara/chimara-if.h", instance_pos = 2.9)]
-       public delegate string ResourceLoadFunc (Chimara.ResourceType usage, uint32 resnum);
-       [CCode (cheader_filename = "libchimara/chimara-glk.h,libchimara/chimara-if.h")]
-       public static GLib.Quark error_quark ();
-}
index 4b0a273f1a5567f583c047ade0d80f12b4e14400..c45859f7b5cb7e8535ed8aa3807158cc0d7bdad1 100644 (file)
  * A stream is opened with a particular file mode, see the 
  * <code>filemode_</code> constants below.
  *
+ * <note><para>
+ *   In the stdio library, using fopen(), %filemode_Write would be mode
+ *   <code>"w"</code>; %filemode_Read would be mode <code>"r"</code>;
+ *   %filemode_ReadWrite would be mode <code>"r+"</code>. Confusingly,
+ *   %filemode_WriteAppend cannot be mode <code>"a"</code>, because the stdio
+ *   spec says that when you open a file with mode <code>"a"</code>, then
+ *   fseek() doesn't work. So we have to use mode <code>"r+"</code> for
+ *   appending. Then we run into the <emphasis>other</emphasis> stdio problem,
+ *   which is that <code>"r+"</code> never creates a new file. So
+ *   %filemode_WriteAppend has to <emphasis>first</emphasis> open the file with
+ *   <code>"a"</code>, close it, reopen with <code>"r+"</code>, and then
+ *   fseek() to the end of the file. For %filemode_ReadWrite, the process is
+ *   the same, except without the fseek() &mdash; we begin at the beginning of
+ *   the file.
+ * </para></note>
+ * <note><para>
+ *   We must also obey an obscure geas of ANSI C <code>"r+"</code> files: you
+ *   can't switch from reading to writing without doing an fseek() in between.
+ *   Switching from writing to reading has the same restriction, except that an
+ *   fflush() also works.
+ * </para></note>
+ *
  * For information on opening streams, see the discussion of each specific type
  * of stream in <link linkend="chimara-The-Types-of-Streams">The Types of
  * Streams</link>. Remember that it is always possible that opening a stream
 
 /**
  * SECTION:glk-stream-types
- * @short_description: Window, memory, and file streams
+ * @short_description: Window, memory, file, and resource streams
  *
  * <refsect2 id="chimara-Window-Streams"><title>Window Streams</title>
  * <para>
  * linkend="chimara-File-References">File References</link>.
  * </para>
  * </refsect2>
+ * <refsect2 id="chimara-Resource-Streams"><title>Resource Streams</title>
+ * <para>
+ * You can open a stream which reads from (but not writes to) a resource file.
+ *
+ * <note><para>
+ *   Typically this is embedded in a Blorb file, as Blorb is the official
+ *   resource-storage format of Glk. A Blorb file can contain images and sounds,
+ *   but it can also contain raw data files, which are accessed by
+ *   glk_stream_open_resource() and glk_stream_open_resource_uni(). A data file
+ *   is identified by number, not by a filename. The Blorb usage field will be
+ *   <code>'Data'</code>. The chunk type will be %giblorb_ID_TEXT for text
+ *   resources, %giblorb_ID_BINA for binary resources.
+ * </para></note>
+ *
+ * <note><para>
+ *   If the running program is not associated with a Blorb file, the library may
+ *   look for data files as actual files instead. These would be named
+ *   <filename>DATA1</filename>, <filename>DATA2</filename>, etc, with a suffix
+ *   distinguishing text and binary files. See <quote>Other Resource
+ *   Arrangements</quote> in the Blorb spec: <ulink
+ *   url="http://eblong.com/zarf/blorb/"></ulink>
+ * </para></note>
+ * </para>
+ * </refsect2>
  */
  
 /**
  * SECTION:glk-sound-testing
  * @short_description: Checking whether the library supports sound
  *
- * Before calling Glk sound functions, you should use the %gestalt_Sound
- * selector. To test for additional capabilities, you can use the 
- * %gestalt_SoundMusic, %gestalt_SoundVolume, and %gestalt_SoundNotify 
- * selectors.
+ * Before calling Glk sound functions, you should use the %gestalt_Sound2
+ * selector.
+ *
+ * Earlier versions of the Glk spec defined separate selectors for various
+ * optional capabilities. This has proven to be an unnecessarily confusing
+ * strategy, and is no longer used. The %gestalt_Sound, %gestalt_SoundMusic,
+ * %gestalt_SoundVolume, and %gestalt_SoundNotify selectors still exist, but you
+ * should not need to test them; the %gestalt_Sound2 selector covers all of
+ * them.
  */
 
 /**
  */
 
 /**
- * GLK_MODULE_SOUND:
+ * GLK_MODULE_SOUND2:
  *
  * If you are writing a C program, there is an additional complication. A 
  * library which does not support sound may not implement the sound functions at
  * even get compile-time errors.
  * 
  * To avoid this, you can perform a preprocessor test for the existence of
- * %GLK_MODULE_SOUND. If this is defined, so are all the functions and constants
+ * %GLK_MODULE_SOUND2. If this is defined, so are all the functions and constants
  * described in this section. If not, not.
- */ 
+ */
+
+/**
+ * GLK_MODULE_SOUND:
+ *
+ * You can perform a preprocessor test for the existence of %GLK_MODULE_SOUND.
+ * If this is defined, so are all the functions and constants described in this
+ * section. If not, not.
+ */
  
 /**
  * GLK_MODULE_HYPERLINKS:
  *   So the version number 78.2.11 would be encoded as 0x004E020B.
  * </para></note>
  *
- * The current Glk specification version is 0.7.2, so this selector will return
- * 0x00000702.
+ * The current Glk specification version is 0.7.4, so this selector will return
+ * 0x00000704.
  *
  * |[
  * glui32 res;
  * may implement both, neither, or only one.  
  */
 
+/**
+ * gestalt_Sound2:
+ *
+ * You can test whether the library supports sound:
+ * |[
+ * glui32 res;
+ * res = glk_gestalt(gestalt_Sound2, 0);
+ * ]|
+ * This returns 1 if the overall suite of sound functions is available. This
+ * includes all the functions defined in <link
+ * linkend="chimara-glk-spec-sound">this chapter</link>. It also includes the
+ * capabilities described below under %gestalt_SoundMusic, %gestalt_SoundVolume,
+ * and %gestalt_SoundNotify.
+ */
+
 /**
  * gestalt_Sound:
  *
- * You can test whether the library supports sound: 
  * |[
  * glui32 res;
  * res = glk_gestalt(gestalt_Sound, 0);
  * glk_sound_load_hint().
  *
  * If this selector returns 0, you should not try to call these functions. They 
- * may have no effect, or they may cause a run-time error. 
+ * may have no effect, or they may cause a run-time error.
+ *
+ * This selector is guaranteed to return 1 if %gestalt_Sound2 does.
  */
 
 /**
  *
  * You can test whether the library supports setting the volume of sound 
  * channels: 
- * |[
- * glui32 res;
- * res = glk_gestalt(gestalt_SoundVolume, 0);
- * ]|
+ * |[ res = glk_gestalt(gestalt_SoundVolume, 0); ]|
  * This selector returns 1 if the glk_schannel_set_volume() function works. If 
- * it returns zero, glk_schannel_set_volume() has no effect.     
+ * it returns zero, glk_schannel_set_volume() has no effect.
+ *
+ * This selector is guaranteed to return 1 if %gestalt_Sound2 does.
  */
 
 /**
  * gestalt_SoundNotify:
  *
  * You can test whether the library supports sound notification events:
- * |[
- * glui32 res;
- * res = glk_gestalt(gestalt_SoundNotify, 0);
- * ]| 
+ * |[ res = glk_gestalt(gestalt_SoundNotify, 0); ]| 
  * This selector returns 1 if the library supports sound notification events. If
- * it returns zero, you will never get such events. 
+ * it returns zero, you will never get such events.
+ *
+ * This selector is guaranteed to return 1 if %gestalt_Sound2 does.
  */
 
 /**
  *   course, an ugly hack. It is a concession to the current state of the Glk 
  *   libraries, some of which can handle AIFF but not MOD sounds.
  * </para></note>
+ *
+ * This selector is guaranteed to return 1 if %gestalt_Sound2 does.
  */ 
   
 /**
  * </para></note>
  */
 
+/**
+ * gestalt_ResourceStream:
+ *
+ * |[
+ * res = glk_gestalt(gestalt_ResourceStream, 0);
+ * ]|
+ *
+ * This returns 1 if the glk_stream_open_resource() and
+ * glk_stream_open_resource_uni() functions are available. If it returns 0, you
+ * should not call them.
+ */
+
 /**
  * evtype_None:
  *
 /**
  * evtype_SoundNotify:
  *
+ * The completion of a sound being played in a sound channel.
+ *
  * On platforms that support sound, you can request to receive an 
  * %evtype_SoundNotify event when a sound finishes playing. See <link
  * linkend="chimara-Playing-Sounds">Playing Sounds</link>.
  
 /**
  * evtype_Hyperlink:
+ *
+ * The selection of a hyperlink in a window.
  * 
  * On platforms that support hyperlinks, you can request to receive an
  * %evtype_Hyperlink event when the player selects a link. See <link
  * Events</link>.
  */
 
+/**
+ * evtype_VolumeNotify:
+ *
+ * The completion of a gradual volume change in a sound channel.
+ *
+ * On platforms that support sound, you can request to receive an
+ * %evtype_VolumeNotify event when a gradual volume change completes. See <link
+ * linkend="chimara-Playing-Sounds">Playing Sounds</link>.
+ */
+
 /**
  * event_t:
  * @type: the event type
  * linefeed-plus-carriage-return combinations; Latin-1 characters may be
  * converted to native character codes. When reading a file in text mode, native
  * line breaks will be converted back to newline (0x0A) characters, and native
- * character codes may be converted to Latin-1. 
+ * character codes may be converted to Latin-1 or UTF-8
  *
  * <note><para>
  *   Line breaks will always be converted; other conversions are more
  * Resource usage constant representing an image file.
  */
 
+/**
+ * giblorb_ID_Data:
+ *
+ * Resource usage constant representing a data file.
+ */
+
 /**
  * giblorb_ID_Copyright:
  *
  * Resource usage constant representing any textual annotation that the user or 
  * writing program sees fit to include.
  */ 
+
+/**
+ * giblorb_ID_TEXT:
+ *
+ * Resource usage constant representing a text data file.
+ */
+
+/**
+ * giblorb_ID_BINA:
+ *
+ * Resource usage constant representing a binary data file.
+ */
+
 /**
  * giblorb_map_t:
  *
index 24e96eb33cb39a21f625b0f75e5bb4c19543d562..d4f2043dc454fc322c627844d22f168060b47e61 100644 (file)
 
 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;
 }
@@ -204,6 +210,11 @@ glk_fileref_create_temp(glui32 usage, glui32 rock)
  *   value is valid before you use it.
  * </para></note>
  *
+ * The recommended file suffixes for files are <filename>.glkdata</filename> for
+ * %fileusage_Data, <filename>.glksave</filename> for %fileusage_SavedGame,
+ * <filename>.txt</filename> for %fileusage_Transcript and
+ * %fileusage_InputRecord.
+ *
  * Returns: A new fileref, or #NULL if the fileref creation failed or the
  * dialog was canceled.
  */
@@ -256,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);
        
@@ -266,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);
 
@@ -292,25 +345,89 @@ glk_fileref_create_by_prompt(glui32 usage, glui32 fmode, glui32 rock)
  * glkunix_set_base_file(), and otherwise in the current working directory.
  * </para></note>
  *
- * Since filenames are highly platform-specific, you should use
- * glk_fileref_create_by_name() with care. It is legal to pass any string in the
- * name argument. However, the library may have to mangle, transform, or
- * truncate the string to make it a legal native filename. 
+ * Earlier versions of the Glk spec specified that the library may have to
+ * extend, truncate, or change your name argument in order to produce a legal
+ * native filename. This remains true. However, since Glk was originally
+ * proposed, the world has largely reached consensus about what a filename looks
+ * like. Therefore, it is worth including some recommended library behavior
+ * here. Libraries that share this behavior will more easily be able to exchange
+ * files, which may be valuable both to authors (distributing data files for
+ * games) and for players (moving data between different computers or different
+ * applications).
+ *
+ * The library should take the given filename argument, and delete any
+ * characters illegal for a filename. This will include all of the following
+ * characters (and more, if the OS requires it): slash, backslash, angle
+ * brackets (less-than and greater-than), colon, double-quote, pipe (vertical
+ * bar), question-mark, asterisk. The library should also truncate the argument
+ * at the first period (delete the first period and any following characters).
+ * If the result is the empty string, change it to the string
+ * <code>"null"</code>.
+ *
+ * It should then append an appropriate suffix, depending on the usage:
+ * <filename>.glkdata</filename> for %fileusage_Data,
+ * <filename>.glksave</filename> for %fileusage_SavedGame,
+ * <filename>.txt</filename> for %fileusage_Transcript and
+ * %fileusage_InputRecord.
+ *
+ * The above behavior is not a requirement of the Glk spec. Older
+ * implementations can continue doing what they do. Some programs (e.g.
+ * web-based interpreters) may not have access to a traditional filesystem at
+ * all, and to them these recommendations will be meaningless.
+ *
+ * On the other side of the coin, the game file should not press these
+ * limitations. Best practice is for the game to pass a filename containing only
+ * letters and digits, beginning with a letter, and not mixing upper and lower
+ * case. Avoid overly-long filenames.
  *
  * <note><para>
- *   For example, if you create two filerefs with the names <quote>File</quote>
- *   and <quote>FILE</quote>, they may wind up pointing to the same file; the
- *   platform may not support case distinctions in file names. Another example:
- *   on a platform where file type is specified by filename suffix, the library
- *   will add an appropriate suffix based on the usage; any suffix in the string
- *   will be overwritten or added to. For that matter, remember that the period
- *   is not a legal character in Acorn filenames...
+ *   The earlier Glk spec gave more stringent recommendations: <quote>No more
+ *   than 8 characters, consisting entirely of upper-case letters and numbers,
+ *   starting with a letter</quote>. The DOS era is safely contained, if not
+ *   over, so this has been relaxed. The I7 manual recommends <quote>23
+ *   characters or fewer</quote>.
  * </para></note>
  *
- * The most conservative approach is to pass a string of no more than 8
- * characters, consisting entirely of upper-case letters and numbers, starting
- * with a letter. You can then be reasonably sure that the resulting filename
- * will display all the characters you specify &mdash; in some form. 
+ * <note><para>
+ *   To address other complications:</para>
+ *   <itemizedlist>
+ *     <listitem><para>
+ *       Some filesystems are case-insensitive. If you create two filerefs with
+ *       the names <filename>File</filename> and <filename>FILE</filename>, they
+ *       may wind up pointing to the same file, or they may not. Avoid doing
+ *       this.
+ *     </para></listitem>
+ *     <listitem><para>
+ *       Some programs will look for all files in the same directory as the
+ *       program itself (or, for interpreted games, in the same directory as the
+ *       game file). Others may keep files in a data-specific directory
+ *       appropriate for the user (e.g., <filename
+ *       class="directory">~/Library</filename> on MacOS).
+ *     </para></listitem>
+ *     <listitem><para>
+ *       If a game interpreter uses a data-specific directory, there is a
+ *       question of whether to use a common location, or divide it into
+ *       game-specific subdirectories. (Or to put it another way: should the
+ *       namespace of named files be per-game or app-wide?) Since data files may
+ *       be exchanged between games, they should be given an app-wide namespace.
+ *       In contrast, saved games should be per-game, as they can never be
+ *       exchanged. Transcripts and input records can go either way.
+ *     </para></listitem>
+ *     <listitem><para>
+ *       When updating an older library to follow these recommendations,
+ *       consider backwards compatibility for games already installed. When
+ *       opening an existing file (that is, not in a write-only mode) it may be
+ *       worth looking under the older name (suffix) if the newer one does not
+ *       already exist.
+ *     </para></listitem>
+ *     <listitem><para>
+ *       Game-save files are already stored with a variety of file suffixes,
+ *       since that usage goes back to the oldest IF interpreters, long
+ *       predating Glk. It is reasonable to treat them in some special way,
+ *       while hewing closer to these recommendations for data files.
+ *     </para></listitem>
+ *   </itemizedlist>
+ * </note>
  *
  * Returns: A new fileref, or %NULL if the fileref creation failed. 
  */
@@ -322,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 */
@@ -331,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);
@@ -346,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;
 }
 
@@ -365,8 +521,8 @@ glk_fileref_create_by_name(glui32 usage, char *name, glui32 rock)
  * not point to the same actual disk file.
  *
  * <note><para>
- *   This generally depends on whether the platform uses suffixes to indicate
- *   file type.
+ *   Most platforms use suffixes to indicate file type, so it typically will
+ *   not. See the earlier comments about recommended file suffixes.
  * </para></note>
  *
  * If you do this, and open both file references for writing, the results are
@@ -386,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);
 }
 
 /**
index c2d8f4666b2124e94f860804a8f96dac3e39a982..03ab4fe94fcd99c5cc98f8eb7ba1da9c0af3aca7 100644 (file)
@@ -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
index 6dbffa1ade892881ac4e7dc3afc46fe83a41b4da..1dad9d94bf8c173b62f6ff906691f159e565ee70 100644 (file)
@@ -6,7 +6,7 @@
 /* Version of the Glk specification implemented by this library */
 #define MAJOR_VERSION 0
 #define MINOR_VERSION 7
-#define SUB_VERSION   2
+#define SUB_VERSION   4
 
 /**
  * glk_gestalt:
@@ -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 */
@@ -130,6 +131,7 @@ glk_gestalt_ext(glui32 sel, glui32 val, glui32 *arr, glui32 arrlen)
                case gestalt_SoundVolume:
                case gestalt_SoundNotify:
                case gestalt_SoundMusic:
+               case gestalt_Sound2:
 #ifdef GSTREAMER_SOUND
                        return 1;
 #else
@@ -137,7 +139,7 @@ glk_gestalt_ext(glui32 sel, glui32 val, glui32 *arr, glui32 arrlen)
 #endif
                        
                /* Unsupported capabilities */
-               /* none! */
+                       /* None! */
 
                /* Selector not supported */    
                default:
index cdee5f7b35f586f84fd0c7283f56ed46d7d2db0a..3f947f92bf1776c8bab6817e12d5cdd39ddcd6b8 100644 (file)
@@ -1,9 +1,9 @@
 /* gi_blorb.c: Blorb library layer for Glk API.
-    gi_blorb version 1.4.
+    gi_blorb version 1.5.
     Designed by Andrew Plotkin <erkyrath@eblong.com>
-    http://www.eblong.com/zarf/glk/index.html
+    http://eblong.com/zarf/glk/
 
-    This file is copyright 1998-2000 by Andrew Plotkin. You may copy,
+    This file is copyright 1998-2010 by Andrew Plotkin. You may copy,
     distribute, and incorporate it into your own programs, by any means
     and under any conditions, as long as you do not modify it. You may
     also modify this file, incorporate it into your own programs,
index ddec1bd76435ba414dd0682feba34d3ad5c72aa2..4e051caab4e6d0714b650189463d8c6cd894e9ba 100644 (file)
@@ -2,11 +2,11 @@
 #define _GI_BLORB_H
 
 /* gi_blorb.h: Blorb library layer for Glk API.
-    gi_blorb version 1.4.
+    gi_blorb version 1.5.
     Designed by Andrew Plotkin <erkyrath@eblong.com>
-    http://www.eblong.com/zarf/glk/index.html
+    http://eblong.com/zarf/glk/
 
-    This file is copyright 1998-2000 by Andrew Plotkin. You may copy,
+    This file is copyright 1998-2012 by Andrew Plotkin. You may copy,
     distribute, and incorporate it into your own programs, by any means
     and under any conditions, as long as you do not modify it. You may
     also modify this file, incorporate it into your own programs,
@@ -35,12 +35,15 @@ typedef glui32 giblorb_err_t;
 #define giblorb_make_id(c1, c2, c3, c4)  \
     (((c1) << 24) | ((c2) << 16) | ((c3) << 8) | (c4))
 
-#define giblorb_ID_Snd       (giblorb_make_id('S', 'n', 'd', ' '))
 #define giblorb_ID_Exec      (giblorb_make_id('E', 'x', 'e', 'c'))
+#define giblorb_ID_Snd       (giblorb_make_id('S', 'n', 'd', ' '))
 #define giblorb_ID_Pict      (giblorb_make_id('P', 'i', 'c', 't'))
+#define giblorb_ID_Data      (giblorb_make_id('D', 'a', 't', 'a'))
 #define giblorb_ID_Copyright (giblorb_make_id('(', 'c', ')', ' '))
 #define giblorb_ID_AUTH      (giblorb_make_id('A', 'U', 'T', 'H'))
 #define giblorb_ID_ANNO      (giblorb_make_id('A', 'N', 'N', 'O'))
+#define giblorb_ID_TEXT      (giblorb_make_id('T', 'E', 'X', 'T'))
+#define giblorb_ID_BINA      (giblorb_make_id('B', 'I', 'N', 'A'))
 
 /* giblorb_map_t: Holds the complete description of an open Blorb 
     file. This type is opaque for normal interpreter use. */
index 4564ae4d6a37a36ad4a84c9c5035fb1ecb602e60..2b7ae0263391481440a6dd57a231f289cf1787c5 100644 (file)
@@ -1,8 +1,8 @@
-/* gi_dispa.c: Dispatch layer for Glk API, version 0.7.2.
+/* gi_dispa.c: Dispatch layer for Glk API, version 0.7.4.
     Designed by Andrew Plotkin <erkyrath@eblong.com>
     http://eblong.com/zarf/glk/
 
-    This file is copyright 1998-2011 by Andrew Plotkin. You may copy,
+    This file is copyright 1998-2012 by Andrew Plotkin. You may copy,
     distribute, and incorporate it into your own programs, by any means
     and under any conditions, as long as you do not modify it. You may
     also modify this file, incorporate it into your own programs,
@@ -49,6 +49,7 @@ static gidispatch_intconst_t intconstant_table[] = {
     { "evtype_Redraw", (6) },
     { "evtype_SoundNotify", (7) },
     { "evtype_Timer", (1) },
+    { "evtype_VolumeNotify", (9) },
 
     { "filemode_Read", (0x02) },
     { "filemode_ReadWrite", (0x03) },
@@ -79,7 +80,9 @@ static gidispatch_intconst_t intconstant_table[] = {
     { "gestalt_LineTerminatorKey", (19) },
     { "gestalt_LineTerminators", (18) },
     { "gestalt_MouseInput", (4) },
+    { "gestalt_ResourceStream", (22) },
     { "gestalt_Sound", (8) },
+    { "gestalt_Sound2", (21) },
     { "gestalt_SoundMusic", (13) },
     { "gestalt_SoundNotify", (10) },
     { "gestalt_SoundVolume", (9) },
@@ -260,6 +263,13 @@ static gidispatch_function_t function_table[] = {
     { 0x00FA, glk_schannel_stop, "schannel_stop" },
     { 0x00FB, glk_schannel_set_volume, "schannel_set_volume" },
     { 0x00FC, glk_sound_load_hint, "sound_load_hint" },
+#ifdef GLK_MODULE_SOUND2
+    { 0x00F4, glk_schannel_create_ext, "schannel_create_ext" },
+    { 0x00F7, glk_schannel_play_multi, "schannel_play_multi" },
+    { 0x00FD, glk_schannel_set_volume_ext, "schannel_set_volume_ext" },
+    { 0x00FE, glk_schannel_pause, "schannel_pause" },
+    { 0x00FF, glk_schannel_unpause, "schannel_unpause" },
+#endif /* GLK_MODULE_SOUND2 */
 #endif /* GLK_MODULE_SOUND */
 #ifdef GLK_MODULE_HYPERLINKS
     { 0x0100, glk_set_hyperlink, "set_hyperlink" },
@@ -307,6 +317,10 @@ static gidispatch_function_t function_table[] = {
     { 0x016E, glk_date_to_simple_time_utc, "date_to_simple_time_utc" },
     { 0x016F, glk_date_to_simple_time_local, "date_to_simple_time_local" },
 #endif /* GLK_MODULE_DATETIME */
+#ifdef GLK_MODULE_RESOURCE_STREAM
+    { 0x0049, glk_stream_open_resource, "stream_open_resource" },
+    { 0x013A, glk_stream_open_resource_uni, "stream_open_resource_uni" },
+#endif /* GLK_MODULE_RESOURCE_STREAM */
 };
 
 glui32 gidispatch_count_classes()
@@ -544,6 +558,19 @@ char *gidispatch_prototype(glui32 funcnum)
             return "2QdIu:";
         case 0x00FC: /* sound_load_hint */
             return "2IuIu:";
+
+#ifdef GLK_MODULE_SOUND2
+        case 0x00F4: /* schannel_create_ext */
+            return "3IuIu:Qd";
+        case 0x00F7: /* schannel_play_multi */
+            return "4>+#Qd>+#IuIu:Iu";
+        case 0x00FD: /* schannel_set_volume_ext */
+            return "4QdIuIuIu:";
+        case 0x00FE: /* schannel_pause */
+            return "1Qd:";
+        case 0x00FF: /* schannel_unpause */
+            return "1Qd:";
+#endif /* GLK_MODULE_SOUND2 */
 #endif /* GLK_MODULE_SOUND */
 
 #ifdef GLK_MODULE_HYPERLINKS
@@ -632,6 +659,13 @@ char *gidispatch_prototype(glui32 funcnum)
             return "3>+[8IsIsIsIsIsIsIsIs]Iu:Is";
 #endif /* GLK_MODULE_DATETIME */
 
+#ifdef GLK_MODULE_RESOURCE_STREAM
+        case 0x0049: /* stream_open_resource */
+            return "3IuIu:Qb";
+        case 0x013A: /* stream_open_resource_uni */
+            return "3IuIu:Qb";
+#endif /* GLK_MODULE_RESOURCE_STREAM */
+
         default:
             return NULL;
     }
@@ -1081,6 +1115,31 @@ void gidispatch_call(glui32 funcnum, glui32 numargs, gluniversal_t *arglist)
         case 0x00FC: /* sound_load_hint */
             glk_sound_load_hint(arglist[0].uint, arglist[1].uint);
             break;
+
+#ifdef GLK_MODULE_SOUND2
+        case 0x00F4: /* schannel_create_ext */
+            arglist[3].opaqueref = glk_schannel_create_ext(arglist[0].uint, arglist[1].uint);
+            break;
+        case 0x00F7: /* schannel_play_multi */
+            if (arglist[0].ptrflag && arglist[3].ptrflag)
+                arglist[8].uint = glk_schannel_play_multi(arglist[1].array, arglist[2].uint, arglist[4].array, arglist[5].uint, arglist[6].uint);
+            else if (arglist[0].ptrflag)
+                arglist[6].uint = glk_schannel_play_multi(arglist[1].array, arglist[2].uint, NULL, 0, arglist[4].uint);
+            else if (arglist[1].ptrflag)
+                arglist[6].uint = glk_schannel_play_multi(NULL, 0, arglist[2].array, arglist[3].uint, arglist[4].uint);
+            else
+                arglist[4].uint = glk_schannel_play_multi(NULL, 0, NULL, 0, arglist[2].uint);
+            break;
+        case 0x00FD: /* schannel_set_volume_ext */
+            glk_schannel_set_volume_ext(arglist[0].opaqueref, arglist[1].uint, arglist[2].uint, arglist[3].uint);
+            break;
+        case 0x00FE: /* schannel_pause */
+            glk_schannel_pause(arglist[0].opaqueref);
+            break;
+        case 0x00FF: /* schannel_unpause */
+            glk_schannel_unpause(arglist[0].opaqueref);
+            break;
+#endif /* GLK_MODULE_SOUND2 */
 #endif /* GLK_MODULE_SOUND */
 
 #ifdef GLK_MODULE_HYPERLINKS
@@ -1425,6 +1484,15 @@ void gidispatch_call(glui32 funcnum, glui32 numargs, gluniversal_t *arglist)
             break;
 #endif /* GLK_MODULE_DATETIME */
 
+#ifdef GLK_MODULE_RESOURCE_STREAM
+        case 0x0049: /* stream_open_resource */
+            arglist[3].opaqueref = glk_stream_open_resource(arglist[0].uint, arglist[1].uint);
+            break;
+        case 0x013A: /* stream_open_resource_uni */
+            arglist[3].opaqueref = glk_stream_open_resource_uni(arglist[0].uint, arglist[1].uint);
+            break;
+#endif /* GLK_MODULE_RESOURCE_STREAM */
+
         default:
             /* do nothing */
             break;
index 6ea045cab6fc0f51468d4e01db123c0064508341..ddb1b19cc7cfdd68bc17ae32d84d3da8c6a37d18 100644 (file)
@@ -1,11 +1,11 @@
 #ifndef _GI_DISPA_H
 #define _GI_DISPA_H
 
-/* gi_dispa.h: Header file for dispatch layer of Glk API, version 0.7.2.
+/* gi_dispa.h: Header file for dispatch layer of Glk API, version 0.7.4.
     Designed by Andrew Plotkin <erkyrath@eblong.com>
-    http://www.eblong.com/zarf/glk/index.html
+    http://eblong.com/zarf/glk/index.html
 
-    This file is copyright 1998-2011 by Andrew Plotkin. You may copy,
+    This file is copyright 1998-2012 by Andrew Plotkin. You may copy,
     distribute, and incorporate it into your own programs, by any means
     and under any conditions, as long as you do not modify it. You may
     also modify this file, incorporate it into your own programs,
index d6ca3273a326ba0043cf5dee9f45d02b857cbb90..f67a9e1f90871aa53b6d385c5b0a269c21bfba7b 100644 (file)
@@ -1,41 +1,44 @@
 #ifndef GLK_H
 #define GLK_H
 
-/* glk.h: Header file for Glk API, version 0.7.2.
- Designed by Andrew Plotkin <erkyrath@eblong.com>
- http://eblong.com/zarf/glk/
This file is copyright 1998-2011 by Andrew Plotkin. You may copy,
- distribute, and incorporate it into your own programs, by any means
- and under any conditions, as long as you do not modify it. You may
- also modify this file, incorporate it into your own programs,
- and distribute the modified version, as long as you retain a notice
- in your program or documentation which mentions my name and the URL
- shown above.
- */
+/* glk.h: Header file for Glk API, version 0.7.4.
   Designed by Andrew Plotkin <erkyrath@eblong.com>
   http://eblong.com/zarf/glk/
+
   This file is copyright 1998-2012 by Andrew Plotkin. You may copy,
   distribute, and incorporate it into your own programs, by any means
   and under any conditions, as long as you do not modify it. You may
   also modify this file, incorporate it into your own programs,
   and distribute the modified version, as long as you retain a notice
   in your program or documentation which mentions my name and the URL
   shown above.
+*/
 
 /* If your system does not have <stdint.h>, you'll have to remove this
- include line. Then edit the definition of glui32 to make sure it's
- really a 32-bit unsigned integer type, and glsi32 to make sure
- it's really a 32-bit signed integer type. If they're not, horrible
- things will happen. */
   include line. Then edit the definition of glui32 to make sure it's
   really a 32-bit unsigned integer type, and glsi32 to make sure
   it's really a 32-bit signed integer type. If they're not, horrible
   things will happen. */
 #include <stdint.h>
 typedef uint32_t glui32;
 typedef int32_t glsi32;
 
 /* These are the compile-time conditionals that reveal various Glk optional
- modules. */
+    modules. Note that if GLK_MODULE_SOUND2 is defined, GLK_MODULE_SOUND
+    must be also. */
 #define GLK_MODULE_LINE_ECHO
 #define GLK_MODULE_LINE_TERMINATORS
 #define GLK_MODULE_UNICODE
 #define GLK_MODULE_UNICODE_NORM
 #define GLK_MODULE_IMAGE
 #define GLK_MODULE_SOUND
+#define GLK_MODULE_SOUND2
 #define GLK_MODULE_HYPERLINKS
 #define GLK_MODULE_DATETIME
+#define GLK_MODULE_RESOURCE_STREAM
 
 /* These types are opaque object identifiers. They're pointers to opaque
- C structures, which are defined differently by each library. */
   C structures, which are defined differently by each library. */
 typedef struct glk_window_struct  *winid_t;
 typedef struct glk_stream_struct  *strid_t;
 typedef struct glk_fileref_struct *frefid_t;
@@ -65,6 +68,8 @@ typedef struct glk_schannel_struct *schanid_t;
 #define gestalt_LineTerminators (18)
 #define gestalt_LineTerminatorKey (19)
 #define gestalt_DateTime (20)
+#define gestalt_Sound2 (21)
+#define gestalt_ResourceStream (22)
 
 #define evtype_None (0)
 #define evtype_Timer (1)
@@ -75,6 +80,7 @@ typedef struct glk_schannel_struct *schanid_t;
 #define evtype_Redraw (6)
 #define evtype_SoundNotify (7)
 #define evtype_Hyperlink (8)
+#define evtype_VolumeNotify (9)
 
 typedef struct event_struct {
     glui32 type;
@@ -185,7 +191,7 @@ typedef struct stream_result_struct {
 #define   stylehint_just_RightFlush (3)
 
 /* glk_main() is the top-level function which you define. The Glk library
- calls it. */
   calls it. */
 extern void glk_main(void);
 
 extern void glk_exit(void);
@@ -194,21 +200,21 @@ extern void glk_tick(void);
 
 extern glui32 glk_gestalt(glui32 sel, glui32 val);
 extern glui32 glk_gestalt_ext(glui32 sel, glui32 val, glui32 *arr,
-                                                         glui32 arrlen);
+    glui32 arrlen);
 
 extern unsigned char glk_char_to_lower(unsigned char ch);
 extern unsigned char glk_char_to_upper(unsigned char ch);
 
 extern winid_t glk_window_get_root(void);
 extern winid_t glk_window_open(winid_t split, glui32 method, glui32 size,
-                                                          glui32 wintype, glui32 rock);
+    glui32 wintype, glui32 rock);
 extern void glk_window_close(winid_t win, stream_result_t *result);
 extern void glk_window_get_size(winid_t win, glui32 *widthptr,
-                                                               glui32 *heightptr);
+    glui32 *heightptr);
 extern void glk_window_set_arrangement(winid_t win, glui32 method,
-                                                                          glui32 size, winid_t keywin);
+    glui32 size, winid_t keywin);
 extern void glk_window_get_arrangement(winid_t win, glui32 *methodptr,
-                                                                          glui32 *sizeptr, winid_t *keywinptr);
+    glui32 *sizeptr, winid_t *keywinptr);
 extern winid_t glk_window_iterate(winid_t win, glui32 *rockptr);
 extern glui32 glk_window_get_rock(winid_t win);
 extern glui32 glk_window_get_type(winid_t win);
@@ -223,9 +229,9 @@ extern strid_t glk_window_get_echo_stream(winid_t win);
 extern void glk_set_window(winid_t win);
 
 extern strid_t glk_stream_open_file(frefid_t fileref, glui32 fmode,
-                                                                       glui32 rock);
+    glui32 rock);
 extern strid_t glk_stream_open_memory(char *buf, glui32 buflen, glui32 fmode,
-                                                                         glui32 rock);
+    glui32 rock);
 extern void glk_stream_close(strid_t str, stream_result_t *result);
 extern strid_t glk_stream_iterate(strid_t str, glui32 *rockptr);
 extern glui32 glk_stream_get_rock(strid_t str);
@@ -248,19 +254,19 @@ extern glui32 glk_get_line_stream(strid_t str, char *buf, glui32 len);
 extern glui32 glk_get_buffer_stream(strid_t str, char *buf, glui32 len);
 
 extern void glk_stylehint_set(glui32 wintype, glui32 styl, glui32 hint,
-                                                         glsi32 val);
+    glsi32 val);
 extern void glk_stylehint_clear(glui32 wintype, glui32 styl, glui32 hint);
 extern glui32 glk_style_distinguish(winid_t win, glui32 styl1, glui32 styl2);
 extern glui32 glk_style_measure(winid_t win, glui32 styl, glui32 hint,
-                                                               glui32 *result);
+    glui32 *result);
 
 extern frefid_t glk_fileref_create_temp(glui32 usage, glui32 rock);
 extern frefid_t glk_fileref_create_by_name(glui32 usage, char *name,
-                                                                                  glui32 rock);
+    glui32 rock);
 extern frefid_t glk_fileref_create_by_prompt(glui32 usage, glui32 fmode,
-                                                                                        glui32 rock);
+    glui32 rock);
 extern frefid_t glk_fileref_create_from_fileref(glui32 usage, frefid_t fref,
-                                                                                               glui32 rock);
+    glui32 rock);
 extern void glk_fileref_destroy(frefid_t fref);
 extern frefid_t glk_fileref_iterate(frefid_t fref, glui32 *rockptr);
 extern glui32 glk_fileref_get_rock(frefid_t fref);
@@ -273,7 +279,7 @@ extern void glk_select_poll(event_t *event);
 extern void glk_request_timer_events(glui32 millisecs);
 
 extern void glk_request_line_event(winid_t win, char *buf, glui32 maxlen,
-                                                                  glui32 initlen);
+    glui32 initlen);
 extern void glk_request_char_event(winid_t win);
 extern void glk_request_mouse_event(winid_t win);
 
@@ -287,17 +293,17 @@ extern void glk_set_echo_line_event(winid_t win, glui32 val);
 
 #ifdef GLK_MODULE_LINE_TERMINATORS
 extern void glk_set_terminators_line_event(winid_t win, glui32 *keycodes, 
-                                                                                  glui32 count);
+    glui32 count);
 #endif /* GLK_MODULE_LINE_TERMINATORS */
 
 #ifdef GLK_MODULE_UNICODE
 
 extern glui32 glk_buffer_to_lower_case_uni(glui32 *buf, glui32 len,
-                                                                                  glui32 numchars);
+    glui32 numchars);
 extern glui32 glk_buffer_to_upper_case_uni(glui32 *buf, glui32 len,
-                                                                                  glui32 numchars);
+    glui32 numchars);
 extern glui32 glk_buffer_to_title_case_uni(glui32 *buf, glui32 len,
-                                                                                  glui32 numchars, glui32 lowerrest);
+    glui32 numchars, glui32 lowerrest);
 
 extern void glk_put_char_uni(glui32 ch);
 extern void glk_put_string_uni(glui32 *s);
@@ -311,22 +317,22 @@ extern glui32 glk_get_buffer_stream_uni(strid_t str, glui32 *buf, glui32 len);
 extern glui32 glk_get_line_stream_uni(strid_t str, glui32 *buf, glui32 len);
 
 extern strid_t glk_stream_open_file_uni(frefid_t fileref, glui32 fmode,
-                                                                               glui32 rock);
+    glui32 rock);
 extern strid_t glk_stream_open_memory_uni(glui32 *buf, glui32 buflen,
-                                                                                 glui32 fmode, glui32 rock);
+    glui32 fmode, glui32 rock);
 
 extern void glk_request_char_event_uni(winid_t win);
 extern void glk_request_line_event_uni(winid_t win, glui32 *buf,
-                                                                          glui32 maxlen, glui32 initlen);
+    glui32 maxlen, glui32 initlen);
 
 #endif /* GLK_MODULE_UNICODE */
 
 #ifdef GLK_MODULE_UNICODE_NORM
 
 extern glui32 glk_buffer_canon_decompose_uni(glui32 *buf, glui32 len,
-                                                                                        glui32 numchars);
+    glui32 numchars);
 extern glui32 glk_buffer_canon_normalize_uni(glui32 *buf, glui32 len,
-                                                                                        glui32 numchars);
+    glui32 numchars);
 
 #endif /* GLK_MODULE_UNICODE_NORM */
 
@@ -340,15 +346,15 @@ extern glui32 glk_buffer_canon_normalize_uni(glui32 *buf, glui32 len,
 
 extern glui32 glk_image_draw(winid_t win, glui32 image, glsi32 val1, glsi32 val2);
 extern glui32 glk_image_draw_scaled(winid_t win, glui32 image,
-                                                                       glsi32 val1, glsi32 val2, glui32 width, glui32 height);
+    glsi32 val1, glsi32 val2, glui32 width, glui32 height);
 extern glui32 glk_image_get_info(glui32 image, glui32 *width, glui32 *height);
 
 extern void glk_window_flow_break(winid_t win);
 
 extern void glk_window_erase_rect(winid_t win,
-                                                                 glsi32 left, glsi32 top, glui32 width, glui32 height);
+    glsi32 left, glsi32 top, glui32 width, glui32 height);
 extern void glk_window_fill_rect(winid_t win, glui32 color,
-                                                                glsi32 left, glsi32 top, glui32 width, glui32 height);
+    glsi32 left, glsi32 top, glui32 width, glui32 height);
 extern void glk_window_set_background_color(winid_t win, glui32 color);
 
 #endif /* GLK_MODULE_IMAGE */
@@ -362,12 +368,25 @@ extern glui32 glk_schannel_get_rock(schanid_t chan);
 
 extern glui32 glk_schannel_play(schanid_t chan, glui32 snd);
 extern glui32 glk_schannel_play_ext(schanid_t chan, glui32 snd, glui32 repeats,
-                                                                       glui32 notify);
+    glui32 notify);
 extern void glk_schannel_stop(schanid_t chan);
 extern void glk_schannel_set_volume(schanid_t chan, glui32 vol);
 
 extern void glk_sound_load_hint(glui32 snd, glui32 flag);
 
+#ifdef GLK_MODULE_SOUND2
+/* Note that this section is nested inside the #ifdef GLK_MODULE_SOUND.
+   GLK_MODULE_SOUND must be defined if GLK_MODULE_SOUND2 is. */
+
+extern schanid_t glk_schannel_create_ext(glui32 rock, glui32 volume);
+extern glui32 glk_schannel_play_multi(schanid_t *chanarray, glui32 chancount,
+    glui32 *sndarray, glui32 soundcount, glui32 notify);
+extern void glk_schannel_pause(schanid_t chan);
+extern void glk_schannel_unpause(schanid_t chan);
+extern void glk_schannel_set_volume_ext(schanid_t chan, glui32 vol,
+    glui32 duration, glui32 notify);
+
+#endif /* GLK_MODULE_SOUND2 */
 #endif /* GLK_MODULE_SOUND */
 
 #ifdef GLK_MODULE_HYPERLINKS
@@ -403,9 +422,9 @@ extern glsi32 glk_current_simple_time(glui32 factor);
 extern void glk_time_to_date_utc(glktimeval_t *time, glkdate_t *date);
 extern void glk_time_to_date_local(glktimeval_t *time, glkdate_t *date);
 extern void glk_simple_time_to_date_utc(glsi32 time, glui32 factor, 
-                                                                               glkdate_t *date);
+    glkdate_t *date);
 extern void glk_simple_time_to_date_local(glsi32 time, glui32 factor, 
-                                                                                 glkdate_t *date);
+    glkdate_t *date);
 extern void glk_date_to_time_utc(glkdate_t *date, glktimeval_t *time);
 extern void glk_date_to_time_local(glkdate_t *date, glktimeval_t *time);
 extern glsi32 glk_date_to_simple_time_utc(glkdate_t *date, glui32 factor);
@@ -413,4 +432,11 @@ extern glsi32 glk_date_to_simple_time_local(glkdate_t *date, glui32 factor);
 
 #endif /* GLK_MODULE_DATETIME */
 
+#ifdef GLK_MODULE_RESOURCE_STREAM
+
+extern strid_t glk_stream_open_resource(glui32 filenum, glui32 rock);
+extern strid_t glk_stream_open_resource_uni(glui32 filenum, glui32 rock);
+
+#endif /* GLK_MODULE_RESOURCE_STREAM */
+
 #endif /* GLK_H */
index 284fc1cd19f74f8f5df9ba622e4feaf26af68a83..ddaf84806c2bed15a65252bab4e9a83c0d4f0164 100644 (file)
@@ -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
+}
index 0c3215a6f3c66f01b1c50bb63a259ff844a49d3f..1ab5a359b68ba304bf3d0a1c5ec90d357bc13ff0 100644 (file)
@@ -63,7 +63,7 @@ load_image_from_blorb(giblorb_result_t resource, glui32 image, gint width, gint
        g_mutex_unlock(glk_data->resource_lock);
 
        info->pixbuf = gdk_pixbuf_loader_get_pixbuf(loader);
-       gdk_pixbuf_ref(info->pixbuf);
+       g_object_ref(info->pixbuf);
 
        g_object_unref(loader);
        return info;
@@ -89,7 +89,7 @@ load_image_from_file(const gchar *filename, glui32 image, gint width, gint heigh
                g_free(info);
                return NULL;
        }
-       gdk_pixbuf_ref(info->pixbuf);
+       g_object_ref(info->pixbuf);
 
        return info;
 }
@@ -131,7 +131,7 @@ load_image_in_cache(glui32 image, gint width, gint height)
 
        if( g_slist_length(glk_data->image_cache) >= IMAGE_CACHE_MAX_NUM ) {
                struct image_info *head = (struct image_info*) glk_data->image_cache->data;
-               gdk_pixbuf_unref(head->pixbuf);
+               g_object_unref(head->pixbuf);
                g_free(head);
                glk_data->image_cache = g_slist_remove_link(glk_data->image_cache, glk_data->image_cache);
        }
@@ -175,7 +175,7 @@ on_pixbuf_closed(GdkPixbufLoader *loader, gpointer data)
 void
 clear_image_cache(struct image_info *data, gpointer user_data)
 {
-       gdk_pixbuf_unref(data->pixbuf);
+       g_object_unref(data->pixbuf);
        g_free(data);
 }
 
index 8cd7b526bddb86b8f401ec3879255b6b3470293f..617adc19eb90b46736ecf817468beadf7db6c80b 100644 (file)
@@ -13,6 +13,8 @@
 #include "resource.h"
 #include "event.h"
 
+#define VOLUME_TIMER_RESOLUTION 1.0 /* In milliseconds */
+
 extern GPrivate *glk_data_key;
 
 #ifdef GSTREAMER_SOUND
@@ -170,10 +172,48 @@ finally:
  * Remember that it is possible that the library will be unable to create a new
  * channel, in which case glk_schannel_create() will return %NULL.
  *
+ * When you create a channel using glk_schannel_create(), it has full volume,
+ * represented by the value 0x10000. Half volume would be 0x8000, three-quarters
+ * volume would be 0xC000, and so on. A volume of zero represents silence.
+ *
+ * You can overdrive the volume of a channel by setting a volume greater than 
+ * 0x10000. However, this is not recommended; the library may be unable to 
+ * increase the volume past full, or the sound may become distorted. You should 
+ * always create sound resources with the maximum volume you will need, and then
+ * reduce the volume when appropriate using the channel-volume calls.
+ *
+ * <note><para>
+ *   Mathematically, these volume changes should be taken as linear
+ *   multiplication of a waveform represented as linear samples. As I
+ *   understand it, linear PCM encodes the sound pressure, and therefore a
+ *   volume of 0x8000 should represent a 6 dB drop.
+ * </para></note>
+ *
  * Returns: A new sound channel, or %NULL.
  */
 schanid_t 
 glk_schannel_create(glui32 rock)
+{
+       return glk_schannel_create_ext(rock, 0x10000);
+}
+
+/**
+ * glk_schannel_create_ext:
+ * @rock: The rock value to give the new sound channel.
+ * @volume: Integer representing the volume; 0x10000 is 100&percnt;.
+ *
+ * The glk_schannel_create_ext() call lets you create a channel with the volume
+ * already set at a given level.
+ *
+ * Not all libraries support glk_schannel_create_ext(). You should test the
+ * %gestalt_Sound2 selector before you rely on it; see <link
+ * linkend="chimara-Testing-for-Sound-Capabilities">Testing for Sound
+ * Capabilities</link>.
+ *
+ * Returns: A new sound channel, or %NULL.
+ */
+schanid_t
+glk_schannel_create_ext(glui32 rock, glui32 volume)
 {
 #ifdef GSTREAMER_SOUND
        ChimaraGlkPrivate *glk_data = g_private_get(glk_data_key);
@@ -213,6 +253,9 @@ glk_schannel_create(glui32 rock)
                goto fail;
        }
 
+       /* Set the initial volume */
+       glk_schannel_set_volume(s, volume);
+
        /* Put the elements in the pipeline and link as many together as we can
         without knowing the type of the audio stream */
        gst_bin_add_many(GST_BIN(s->pipeline), s->source, s->typefind, s->convert, s->filter, s->sink, NULL);
@@ -379,9 +422,14 @@ glk_schannel_play(schanid_t chan, glui32 snd)
  * sound is playing, there will be no notification event.
  *
  * Not all libraries support sound notification. You should test the
- * %gestalt_SoundNotify selector before you rely on it; see <link
+ * %gestalt_Sound2 selector before you rely on it; see <link
  * linkend="chimara-Testing-for-Sound-Capabilities">Testing for Sound 
  * Capabilities</link>.
+ *
+ * Note that you can play a sound on a channel whose volume is zero. This has
+ * no audible result, unless you later change the volume; but it produces
+ * notifications as usual. You can also play a sound on a paused channel; the
+ * sound is paused immediately, and does not progress.
  * 
  * Returns: 1 on success, 0 on failure.
  */
@@ -389,7 +437,6 @@ glui32
 glk_schannel_play_ext(schanid_t chan, glui32 snd, glui32 repeats, glui32 notify)
 {
        VALID_SCHANNEL(chan, return 0);
-       g_printerr("Play sound %d with repeats %d and notify %d\n", snd, repeats, notify);
 #ifdef GSTREAMER_SOUND
        ChimaraGlkPrivate *glk_data = g_private_get(glk_data_key);
        GInputStream *stream;
@@ -442,8 +489,9 @@ glk_schannel_play_ext(schanid_t chan, glui32 snd, glui32 repeats, glui32 notify)
        g_object_set(chan->source, "stream", stream, NULL);
        g_object_unref(stream); /* Now owned by GStreamer element */
        
-       if(!gst_element_set_state(chan->pipeline, GST_STATE_PLAYING)) {
-               WARNING_S(_("Could not set GstElement state to"), "PLAYING");
+       /* Play the sound; unless the channel is paused, then pause it instead */
+       if(!gst_element_set_state(chan->pipeline, chan->paused? GST_STATE_PAUSED : GST_STATE_PLAYING)) {
+               WARNING_S(_("Could not set GstElement state to"), chan->paused? "PAUSED" : "PLAYING");
                return 0;
        }
        return 1;
@@ -452,6 +500,125 @@ glk_schannel_play_ext(schanid_t chan, glui32 snd, glui32 repeats, glui32 notify)
 #endif
 }
 
+/**
+ * glk_schannel_play_multi:
+ * @chanarray: Array of #schanid_t structures.
+ * @chancount: Length of @chanarray.
+ * @sndarray: Array of sound resource numbers.
+ * @soundcount: Length of @sndarray, must be equal to @chanarray.
+ * @notify: If nonzero, request a notification when each sound finishes.
+ *
+ * This works the same as glk_schannel_play_ext(), except that you can specify
+ * more than one sound. The channel references and sound resource numbers are
+ * given as two arrays, which must be the same length. The @notify argument
+ * applies to all the sounds; the repeats value for all the sounds is 1.
+ * 
+ * All the sounds will begin at exactly the same time.
+ * 
+ * This returns the number of sounds that began playing correctly. (This will be
+ * a number from 0 to @soundcount.)
+ *
+ * <note><para>
+ *   If the @notify argument is nonzero, you will get a separate sound
+ *   notification event as each sound finishes. They will all have the same
+ *   @val2 value.
+ * </para></note>
+ * <note><para>
+ *   Note that you have to supply @chancount and @soundcount as separate
+ *   arguments, even though they are required to be the same. This is an awkward
+ *   consequence of the way array arguments are dispatched in Glulx.
+ * </para></note>
+ * 
+ * Returns: The number of sounds that started playing correctly.
+ */
+glui32
+glk_schannel_play_multi(schanid_t *chanarray, glui32 chancount, glui32 *sndarray, glui32 soundcount, glui32 notify)
+{
+       g_return_val_if_fail(chancount == soundcount, 0);
+       g_return_val_if_fail(chanarray != NULL || chancount == 0, 0);
+       g_return_val_if_fail(sndarray != NULL || soundcount == 0, 0);
+
+       int count;
+       for(count = 0; count < chancount; count++)
+               VALID_SCHANNEL(chanarray[count], return 0);
+
+#ifdef GSTREAMER_SOUND
+       ChimaraGlkPrivate *glk_data = g_private_get(glk_data_key);
+       GInputStream *stream;
+
+       if(!glk_data->resource_map && !glk_data->resource_load_callback) {
+               WARNING(_("No resource map has been loaded yet."));
+               return 0;
+       }
+
+       /* We keep an array of sounds to skip if any of them have errors */
+       gboolean *skiparray = g_new0(gboolean, chancount);
+
+       /* Set up all the channels one by one */
+       for(count = 0; count < chancount; count++) {
+               /* Stop the previous sound */
+               clean_up_after_playing_sound(chanarray[count]);
+
+               /* Load the sound into a GInputStream, by whatever method */
+               if(!glk_data->resource_map) {
+                       gchar *filename = glk_data->resource_load_callback(CHIMARA_RESOURCE_SOUND, sndarray[count], glk_data->resource_load_callback_data);
+                       if(!filename) {
+                               WARNING(_("Error loading resource from alternative location."));
+                               skiparray[count] = TRUE;
+                               continue;
+                       }
+
+                       GError *err = NULL;
+                       GFile *file = g_file_new_for_path(filename);
+                       stream = G_INPUT_STREAM(g_file_read(file, NULL, &err));
+                       if(!stream) {
+                               IO_WARNING(_("Error loading resource from file"), filename, err->message);
+                               g_free(filename);
+                               g_object_unref(file);
+                               skiparray[count] = TRUE;
+                               continue;
+                       }
+                       g_free(filename);
+                       g_object_unref(file);
+               } else {
+                       giblorb_result_t resource;
+                       giblorb_err_t result = giblorb_load_resource(glk_data->resource_map, giblorb_method_Memory, &resource, giblorb_ID_Snd, sndarray[count]);
+                       if(result != giblorb_err_None) {
+                               WARNING_S( _("Error loading resource"), giblorb_get_error_message(result) );
+                               skiparray[count] = TRUE;
+                               continue;
+                       }
+                       stream = g_memory_input_stream_new_from_data(resource.data.ptr, resource.length, NULL);
+               }
+
+               chanarray[count]->repeats = 1;
+               chanarray[count]->resource = sndarray[count];
+               chanarray[count]->notify = notify;
+               g_object_set(chanarray[count]->source, "stream", stream, NULL);
+               g_object_unref(stream); /* Now owned by GStreamer element */
+       }
+
+       /* Start all the sounds as close to each other as possible. */
+       /* FIXME: Is there a way to start them exactly at the same time? */
+       glui32 successes = 0;
+       for(count = 0; count < chancount; count++) {
+               if(skiparray[count])
+                       continue;
+               /* Play the sound; unless the channel is paused, then pause it instead */
+               if(!gst_element_set_state(chanarray[count]->pipeline, chanarray[count]->paused? GST_STATE_PAUSED : GST_STATE_PLAYING)) {
+                       WARNING_S(_("Could not set GstElement state to"), chanarray[count]->paused? "PAUSED" : "PLAYING");
+                       skiparray[count] = TRUE;
+                       continue;
+               }
+               successes++;
+       }
+       g_free(skiparray);
+       return successes;
+#else
+       return 0;
+#endif
+}
+
 /**
  * glk_schannel_stop:
  * @chan: Channel to silence.
@@ -468,24 +635,101 @@ glk_schannel_stop(schanid_t chan)
 #endif
 }
 
+/**
+ * glk_schannel_pause:
+ * @chan: Channel to pause.
+ *
+ * Pause any sound playing in the channel. This does not generate any
+ * notification events. If the channel is already paused, this does nothing.
+ * 
+ * New sounds started in a paused channel are paused immediately.
+ * 
+ * A volume change in progress is <emphasis>not</emphasis> paused, and may
+ * proceed to completion, generating a notification if appropriate.
+ */
+void
+glk_schannel_pause(schanid_t chan)
+{
+       VALID_SCHANNEL(chan, return);
+
+       if(chan->paused)
+               return; /* Silently do nothing */
+
+       /* Mark the channel as paused even if there is no sound playing yet */
+       chan->paused = TRUE;
+
+       GstState state;
+       if(gst_element_get_state(chan->pipeline, &state, NULL, GST_CLOCK_TIME_NONE) != GST_STATE_CHANGE_SUCCESS) {
+               WARNING(_("Could not get GstElement state"));
+               return;
+       }
+       if(state != GST_STATE_PLAYING)
+               return; /* Silently do nothing if no sound is playing */
+
+       if(!gst_element_set_state(chan->pipeline, GST_STATE_PAUSED)) {
+               WARNING_S(_("Could not set GstElement state to"), "PAUSED");
+               return;
+       }
+}
+
+/**
+ * glk_schannel_unpause:
+ * @chan: Channel to unpause.
+ *
+ * Unpause the channel. Any paused sounds begin playing where they left off. If
+ * the channel is not already paused, this does nothing.
+ *
+ * <note><para>
+ *   This means, for example, that you can pause a channel that is currently
+ *   not playing any sounds. If you then add a sound to the channel, it will
+ *   not start playing; it will be paused at its beginning. If you later
+ *   unpaise the channel, the sound will commence.
+ * </para></note>
+ */
+void
+glk_schannel_unpause(schanid_t chan)
+{
+       VALID_SCHANNEL(chan, return);
+
+       if(!chan->paused)
+               return; /* Silently do nothing */
+
+       /* Mark the channel as not paused in any case */
+       chan->paused = FALSE;
+
+       GstState state;
+       if(gst_element_get_state(chan->pipeline, &state, NULL, GST_CLOCK_TIME_NONE) != GST_STATE_CHANGE_SUCCESS) {
+               WARNING(_("Could not get GstElement state"));
+               return;
+       }
+       if(state != GST_STATE_PAUSED)
+               return; /* Silently do nothing */
+
+       if(!gst_element_set_state(chan->pipeline, GST_STATE_PLAYING)) {
+               WARNING_S(_("Could not set GstElement state to"), "PLAYING");
+               return;
+       }
+}
+
 /**
  * glk_schannel_set_volume:
  * @chan: Channel to set the volume of.
  * @vol: Integer representing the volume; 0x10000 is 100&percnt;.
  *
- * Sets the volume in the channel. When you create a channel, it has full 
- * volume, represented by the value 0x10000. Half volume would be 0x8000, 
- * three-quarters volume would be 0xC000, and so on. A volume of zero represents
- * silence, although the sound is still considered to be playing.
+ * Sets the volume in the channel, from 0 (silence) to 0x10000 (full volume).
+ * Again, you can overdrive the volume by setting a value greater than 0x10000,
+ * but this is not recommended.
+ *
+ * The glk_schannel_set_volume() function does not include duration and notify
+ * values. Both are assumed to be zero: immediate change, no notification.
  *
- * You can call this function between sounds, or while a sound is playing. The 
- * effect is immediate.
+ * You can call this function between sounds, or while a sound is playing.
+ * However, a zero-duration change while a sound is playing may produce
+ * unpleasant clicks.
  * 
- * You can overdrive the volume of a channel by setting a volume greater than 
- * 0x10000. However, this is not recommended; the library may be unable to 
- * increase the volume past full, or the sound may become distorted. You should 
- * always create sound resources with the maximum volume you will need, and then
- * call glk_schannel_set_volume() to reduce the volume when appropriate.
+ * At most one volume change can be occurring on a sound channel at any time.
+ * If you call this function while a previous volume change is in progress, the
+ * previous change is interrupted.
  *
  * Not all libraries support this function. You should test the
  * %gestalt_SoundVolume selector before you rely on it; see <link
@@ -499,12 +743,115 @@ glk_schannel_stop(schanid_t chan)
  */
 void 
 glk_schannel_set_volume(schanid_t chan, glui32 vol)
+{
+       glk_schannel_set_volume_ext(chan, vol, 0, 0);
+}
+
+static double
+volume_glk_to_gstreamer(glui32 volume_glk)
+{
+       return CLAMP(((double)volume_glk / 0x10000), 0.0, 10.0);
+}
+
+#ifdef GSTREAMER_SOUND
+static gboolean
+volume_change_timeout(schanid_t chan)
+{
+       GTimeVal now;
+       g_get_current_time(&now);
+
+       if(now.tv_sec >= chan->target_time_sec && now.tv_usec >= chan->target_time_usec) {
+               /* We're done - make sure the volume is at the requested level */
+               g_object_set(chan->filter, "volume", chan->target_volume, NULL);
+
+               if(chan->volume_notify)
+                       event_throw(chan->glk, evtype_VolumeNotify, NULL, 0, chan->volume_notify);
+
+               chan->volume_timer_id = 0;
+               return FALSE;
+       }
+
+       /* Calculate the appropriate step every time - a busy system may delay or
+        * drop timer ticks */
+       double time_left_msec = (chan->target_time_sec - now.tv_sec) * 1000.0
+               + (chan->target_time_usec - now.tv_usec) / 1000.0;
+       double steps_left = time_left_msec / VOLUME_TIMER_RESOLUTION;
+       double current_volume;
+       g_object_get(chan->filter, "volume", &current_volume, NULL);
+       double volume_step = (chan->target_volume - current_volume) / steps_left;
+
+       g_object_set(chan->filter, "volume", current_volume + volume_step, NULL);
+
+       return TRUE;
+}
+#endif /* GSTREAMER_SOUND */
+
+/**
+ * glk_schannel_set_volume_ext:
+ * @chan: Channel to set the volume of.
+ * @vol: Integer representing the volume; 0x10000 is 100&percnt;.
+ * @duration: Length of volume change in milliseconds, or 0 for immediate.
+ * @notify: If nonzero, requests a notification when the volume change finishes.
+ *
+ * Sets the volume in the channel, from 0 (silence) to 0x10000 (full volume).
+ * Again, you can overdrive the volume by setting a value greater than 0x10000,
+ * but this is not recommended.
+ *
+ * If the @duration is zero, the change is immediate. Otherwise, the change
+ * begins immediately, and occurs smoothly over the next @duration milliseconds.
+ *
+ * The @notify value should be nonzero in order to request a volume notification
+ * event. If you do this, when the volume change is completed, you will get an
+ * event with type #evtype_VolumeNotify. The window will be %NULL, @val1 will be
+ * zero, and @val2 will be the nonzero value you passed as @notify.
+ *
+ * You can call this function between sounds, or while a sound is playing.
+ * However, a zero-duration change while a sound is playing may produce
+ * unpleasant clicks.
+ *
+ * At most one volume change can be occurring on a sound channel at any time. If
+ * you call this function while a previous volume change is in progress, the
+ * previous change is interrupted. The beginning point of the new volume change
+ * should be wherever the previous volume change was interrupted (rather than
+ * the previous change's beginning or ending point).
+ *
+ * Not all libraries support these functions. You should test the appropriate
+ * gestalt selectors before you rely on them; see "Testing for Sound
+ * Capabilities".
+ */
+void
+glk_schannel_set_volume_ext(schanid_t chan, glui32 vol, glui32 duration, glui32 notify)
 {
        VALID_SCHANNEL(chan, return);
+       /* Silently ignore out-of-range volume values */
+
 #ifdef GSTREAMER_SOUND
-       gdouble volume_gst = (gdouble)vol / 0x10000;
-       g_printerr("Volume set to: %f\n", volume_gst);
-       g_object_set(chan->filter, "volume", CLAMP(volume_gst, 0.0, 10.0), NULL);
+       /* Interrupt a previous volume change */
+       if(chan->volume_timer_id > 0)
+               g_source_remove(chan->volume_timer_id);
+       
+       double target_volume = volume_glk_to_gstreamer(vol);
+
+       if(duration == 0) {
+               g_object_set(chan->filter, "volume", target_volume, NULL);
+
+               if(notify != 0)
+                       event_throw(chan->glk, evtype_VolumeNotify, NULL, 0, notify);
+
+               return;
+       }
+
+       GTimeVal target_time;
+       g_get_current_time(&target_time);
+       g_time_val_add(&target_time, (long)duration * 1000);
+
+       chan->target_volume = target_volume;
+       chan->target_time_sec = target_time.tv_sec;
+       chan->target_time_usec = target_time.tv_usec;
+       chan->volume_notify = notify;
+
+       /* Set up a timer for the volume */
+       chan->volume_timer_id = g_timeout_add(VOLUME_TIMER_RESOLUTION, (GSourceFunc)volume_change_timeout, chan);
 #endif
 }
 
index 465e7dc51fe9b7095dbf870ffec104dd0a14bc2a..a0eba8715520ac91d800fd8c9614c563397d32f0 100644 (file)
@@ -25,11 +25,19 @@ struct glk_schannel_struct
        glui32 resource, notify;
        /* How many times to repeat the last sound played (-1 = forever) */
        glui32 repeats;
+       /* Whether channel is paused */
+       gboolean paused;
        
+       /* Volume change information */
+       double target_volume;
+       long target_time_sec, target_time_usec;
+       guint volume_timer_id;
+       glui32 volume_notify;
+
 #ifdef GSTREAMER_SOUND
        /* Each sound channel is represented as a GStreamer pipeline.  */
        GstElement *pipeline, *source, *typefind, *demux, *decode, *convert, *filter, *sink;
 #endif
 };
 
-#endif
\ No newline at end of file
+#endif
index 96ab73fb08dcc97c8857541c0d91bafedbe7b33b..0b1e2d8952c8fe34c0d638fe1094a4aba0ed1e28 100644 (file)
@@ -1,10 +1,13 @@
+#include <config.h>
 #include "stream.h"
 #include "fileref.h"
 #include "magic.h"
+#include "gi_blorb.h"
 #include <errno.h>
 #include <stdio.h>
 #include <glib.h>
 #include <glib/gstdio.h>
+#include <glib/gi18n-lib.h>
 
 #include "chimara-glk-private.h"
 extern GPrivate *glk_data_key;
@@ -488,6 +491,122 @@ glk_stream_open_file_uni(frefid_t fileref, glui32 fmode, glui32 rock)
        return file_stream_new(fileref, fmode, rock, TRUE);
 }
 
+/**
+ * glk_stream_open_resource:
+ * @filenum: Resource chunk number to open.
+ * @rock: The new stream's rock value.
+ *
+ * Open the given data resource for reading (only), as a normal stream.
+ *
+ * <note><para>
+ *   Note that there is no notion of file usage &mdash; the resource does not
+ *   have to be specified as <quote>saved game</quote> or whatever.
+ * </para></note>
+ *
+ * If no resource chunk of the given number exists, the open function returns
+ * %NULL.
+ *
+ * As with file streams, a binary resource stream reads the resource as bytes. A
+ * text resource stream reads characters encoded as Latin-1.
+ *
+ * When reading from a resource stream, newlines are not remapped, even if they
+ * normally would be when reading from a text file on the host OS. If you read a
+ * line (glk_get_line_stream() or glk_get_line_stream_uni()), a Unix newline
+ * (0x0A) terminates the line.
+ *
+ * Returns: the new stream, or %NULL.
+ */
+strid_t
+glk_stream_open_resource(glui32 filenum, glui32 rock)
+{
+       /* 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;
+}
+
+/**
+ * glk_stream_open_resource_uni:
+ * @filenum: Resource chunk number to open.
+ * @rock: The new stream's rock value.
+ *
+ * Open the given data resource for reading (only), as a Unicode stream. See
+ * glk_stream_open_resource() for more information.
+ *
+ * As with file streams, a binary resource stream reads the resource as
+ * four-byte (big-endian) words. A text resource stream reads characters encoded
+ * as UTF-8.
+ *
+ * <note><para>
+ *   Thus, the difference between text and binary resources is only important
+ *   when opened as a Unicode stream.
+ * </para></note>
+ *
+ * Returns: the new stream, or %NULL.
+ */
+strid_t
+glk_stream_open_resource_uni(glui32 filenum, glui32 rock)
+{
+       /* 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;
+}
+
 /**
  * glk_stream_close:
  * @str: Stream to close.
@@ -534,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;
index 7ff7618c61ef44dc78a75ebc1b60033f45c81718..33497ceb2d11f36b17cc1afedde5b59cc66fe7e4 100644 (file)
@@ -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 */
 
index 41146f24fd6613bb159cc7d653093bcbaf5f5835..d8dda82a00627d273e705ffd8cf3e0ec2ed0fbf0 100644 (file)
@@ -1,3 +1,4 @@
+#include <config.h>
 #include "charset.h"
 #include "magic.h"
 #include "stream.h"
@@ -6,6 +7,7 @@
 #include <string.h>
 #include <glib.h>
 #include <glib/gstdio.h>
+#include <glib/gi18n-lib.h>
 
 /* 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)
                        {
index 5a194f4572bfc3fc62a0a7aaaa4854737f6d0209..ef85e3f2651f4de44bd99798c490b40d36f2926c 100644 (file)
@@ -416,16 +416,16 @@ glk_window_get_root()
  *  A   C   
  * </literallayout></textobject></mediaobject></entry> 
  * </row></tbody></tgroup></informaltable>
- * After the first split, the new pair window (O1, which covers the whole
- * screen) knows that its first child (A) is above the second, and gets 50% of
- * its own area. (A is the key window for this split, but a proportional split
- * doesn't care about key windows.)
+ * The initial window is A. After the first split, the new pair window (O1,
+ * which covers the whole screen) knows that its new child (B) is below A, and
+ * gets 50% of its own area. (B is the key window for this split, but a
+ * proportional split doesn't care about key windows.)
  * 
- * After the second split, all this remains true; O1 knows that its first child
- * gets 50% of its space, and A is O1's key window. But now O1's first child is
- * O2 instead of A. The newer pair window (O2) knows that its first child (C)
- * is above the second, and gets a fixed size of two rows. (As measured in C's
- * font, because C is O2's key window.)
+ * After the <emphasis>second</emphasis> split, all this remains true; O1 knows
+ * that its first child gets 50% of its space, and B is O1's key window. But
+ * now O1's first child is O2 instead of A. The newer pair window (O2) knows
+ * that its first child (C) is above the second, and gets a fixed size of two
+ * rows. (As measured in C's font, because C is O2's key window.)
  * 
  * If we split C, now, the resulting pair will still be two C-font rows high
  * &mdash; that is, tall enough for two lines of whatever font C displays. For
index 588f18325395ffa69e5daa083f8acbe441bdc87d..e7aee92c58fcc308e32ba9e1812839e97c0169c0 100644 (file)
@@ -29,5 +29,12 @@ gsettings_SCHEMAS = org.chimara-if.gschema.xml
 
 endif
 
-EXTRA_DIST = $(gsettings_SCHEMAS)
+CLEANFILES = config.pyc
+DISTCLEANFILES = config.py
+
+EXTRA_DIST = \
+       $(gsettings_SCHEMAS) \
+       config.py \
+       player.py
+
 -include $(top_srcdir)/git.mk
diff --git a/player/chimara.menus b/player/chimara.menus
new file mode 100644 (file)
index 0000000..085975c
--- /dev/null
@@ -0,0 +1,37 @@
+<?xml version="1.0"?>
+<ui>
+  <menubar>
+    <menu action="game">
+      <menuitem action="open"/>
+         <menuitem action="recent"/>
+      <separator/>
+      <menuitem action="stop"/>
+      <menuitem action="quit_chimara"/>
+    </menu>
+    <menu action="edit">
+      <menuitem action="copy"/>
+      <menuitem action="paste"/>
+      <separator/>
+      <menuitem action="preferences"/>
+    </menu>
+    <menu action="view">
+      <menuitem action="toolbar"/>
+    </menu>
+    <menu action="command">
+      <menuitem action="undo"/>
+      <menuitem action="save"/>
+      <menuitem action="restore"/>
+      <menuitem action="restart"/>
+      <menuitem action="quit"/>
+    </menu>
+    <menu action="help">
+      <menuitem action="about"/>
+    </menu>
+  </menubar>
+  <toolbar>
+    <toolitem action="open"/>
+    <separator/>
+    <toolitem action="restore"/>
+    <toolitem action="save"/>
+  </toolbar>
+</ui>
diff --git a/player/chimara.menus.in b/player/chimara.menus.in
deleted file mode 100644 (file)
index cb383f7..0000000
+++ /dev/null
@@ -1,37 +0,0 @@
-<?xml version="1.0"?>
-<ui>
-  <menubar>
-    <menu action="game">
-      <menuitem action="open"/>
-         @OPEN_RECENT_MENU_ITEM@
-      <separator/>
-      <menuitem action="stop"/>
-      <menuitem action="quit_chimara"/>
-    </menu>
-    <menu action="edit">
-      <menuitem action="copy"/>
-      <menuitem action="paste"/>
-      <separator/>
-      <menuitem action="preferences"/>
-    </menu>
-    <menu action="view">
-      <menuitem action="toolbar"/>
-    </menu>
-    <menu action="command">
-      <menuitem action="undo"/>
-      <menuitem action="save"/>
-      <menuitem action="restore"/>
-      <menuitem action="restart"/>
-      <menuitem action="quit"/>
-    </menu>
-    <menu action="help">
-      <menuitem action="about"/>
-    </menu>
-  </menubar>
-  <toolbar>
-    <toolitem action="open"/>
-    <separator/>
-    <toolitem action="restore"/>
-    <toolitem action="save"/>
-  </toolbar>
-</ui>
diff --git a/player/config.py.in b/player/config.py.in
new file mode 100644 (file)
index 0000000..1d4261d
--- /dev/null
@@ -0,0 +1 @@
+PACKAGE_VERSION = '''@PACKAGE_VERSION@'''
index bd007b4ee128b0a7966822a37c547ca98c7b7c85..d76ef56b6d3884338878c63b8b0f9f93aa1c60c4 100644 (file)
@@ -95,7 +95,7 @@ change_window_title(ChimaraGlk *glk, GParamSpec *pspec, GtkWindow *window)
        g_free(title);
 }
 
-static void
+static gboolean
 create_window(void)
 {
        GError *error = NULL;
@@ -107,8 +107,7 @@ create_window(void)
                error = NULL;
                if( !gtk_builder_add_from_file(builder, PACKAGE_SRC_DIR "/chimara.ui", &error) ) {
 #endif /* DEBUG */
-                       error_dialog(NULL, error, "Error while building interface: ");  
-                       return;
+                       return FALSE;
 #ifdef DEBUG
                }
 #endif /* DEBUG */
@@ -144,13 +143,9 @@ create_window(void)
 #ifdef DEBUG
                g_error_free(error);
                error = NULL;
-               if( !gtk_ui_manager_add_ui_from_file(uimanager, PACKAGE_SRC_DIR "/chimara.menus", &error) ) {
-#endif /* DEBUG */
-                       error_dialog(NULL, error, "Error while building interface: ");
-                       return;
-#ifdef DEBUG
-               }
+               if( !gtk_ui_manager_add_ui_from_file(uimanager, PACKAGE_SRC_DIR "/chimara.menus", &error) )
 #endif /* DEBUG */
+                       return FALSE;
        }
 
        glk = chimara_if_new();
@@ -162,13 +157,9 @@ create_window(void)
 #ifdef DEBUG
                g_error_free(error);
                error = NULL;
-               if( !chimara_glk_set_css_from_file(CHIMARA_GLK(glk), PACKAGE_SRC_DIR "/style.css", &error) ) {
-#endif /* DEBUG */
-                       error_dialog(NULL, error, "Couldn't open CSS file: ");
-                       return;
-#ifdef DEBUG
-               }
+               if( !chimara_glk_set_css_from_file(CHIMARA_GLK(glk), PACKAGE_SRC_DIR "/style.css", &error) )
 #endif /* DEBUG */
+                       return FALSE;
        }
        
        /* DON'T UNCOMMENT THIS your eyes will burn
@@ -178,10 +169,7 @@ create_window(void)
        
        GtkBox *vbox = GTK_BOX( gtk_builder_get_object(builder, "vbox") );                      
        if(vbox == NULL)
-       {
-               error_dialog(NULL, NULL, "Could not find vbox");
-               return;
-       }
+               return FALSE;
 
        gtk_ui_manager_insert_action_group(uimanager, actiongroup, 0);
        GtkWidget *menubar = gtk_ui_manager_get_widget(uimanager, "/menubar");
@@ -206,6 +194,8 @@ create_window(void)
        
        /* Create preferences window */
        preferences_create(CHIMARA_GLK(glk));
+
+       return TRUE;
 }
 
 int
@@ -243,7 +233,10 @@ main(int argc, char *argv[])
        state_settings = g_settings_new_with_backend("org.chimara-if.player.state", backend);
        g_free(keyfile);
 
-       create_window();
+       if( !create_window() ) {
+               error_dialog(NULL, NULL, "Error while building interface.");
+               return 1;
+       }
        gtk_widget_show_all(window);
 
        g_object_unref( G_OBJECT(uimanager) );
diff --git a/player/player.py b/player/player.py
new file mode 100644 (file)
index 0000000..82a6195
--- /dev/null
@@ -0,0 +1,309 @@
+#!/usr/bin/env python
+
+import sys
+import os.path
+from gi.repository import GObject, GLib, Gdk, Gio, Gtk, Chimara
+import config
+
+# FIXME: Dummy translation function, for now
+_ = lambda x: x
+
+
+class Player(GObject.GObject):
+    __gtype_name__ = 'ChimaraPlayer'
+
+    def __init__(self):
+        super(Player, self).__init__()
+
+        # FIXME: should use the Keyfile backend, but that's not available from
+        # Python
+        self.prefs_settings = Gio.Settings('org.chimara-if.player.preferences')
+        self.state_settings = Gio.Settings('org.chimara-if.player.state')
+
+        builder = Gtk.Builder()
+        builder.add_from_file('chimara.ui')
+        self.window = builder.get_object('chimara')
+        self.aboutwindow = builder.get_object('aboutwindow')
+        self.prefswindow = builder.get_object('prefswindow')
+        actiongroup = builder.get_object('actiongroup')
+
+        # Set the default value of the "View/Toolbar" menu item upon creation
+        # of a new window to the "show-toolbar-default" setting, but bind the
+        # setting one-way only - we don't want toolbars to disappear suddenly
+        toolbar_action = builder.get_object('toolbar')
+        toolbar_action.active = \
+            self.state_settings.get_boolean('show-toolbar-default')
+        self.state_settings.bind('show-toolbar-default', toolbar_action,
+            'active', Gio.SettingsBindFlags.SET)
+
+        filt = Gtk.RecentFilter()
+        for pattern in ['*.z[1-8]', '*.[zg]lb', '*.[zg]blorb', '*.ulx', '*.blb',
+            '*.blorb']:
+            filt.add_pattern(pattern)
+        recent = builder.get_object('recent')
+        recent.add_filter(filt)
+
+        uimanager = Gtk.UIManager()
+        uimanager.add_ui_from_file('chimara.menus')
+        uimanager.insert_action_group(actiongroup, 0)
+        menubar = uimanager.get_widget('/menubar')
+        toolbar = uimanager.get_widget('/toolbar')
+        toolbar.no_show_all = True
+        if toolbar_action.active:
+            toolbar.show()
+        else:
+            toolbar.hide()
+
+        # Connect the accelerators
+        accels = uimanager.get_accel_group()
+        self.window.add_accel_group(accels)
+
+        self.glk = Chimara.IF()
+        self.glk.props.ignore_errors = True
+        self.glk.set_css_from_file('style.css')
+
+        vbox = builder.get_object('vbox')
+        vbox.pack_end(self.glk, True, True, 0)
+        vbox.pack_start(menubar, False, False, 0)
+        vbox.pack_start(toolbar, False, False, 0)
+
+        #builder.connect_signals(self)  # FIXME Segfaults?!
+        builder.get_object('open').connect('activate', self.on_open_activate)
+        builder.get_object('restore').connect('activate',
+            self.on_restore_activate)
+        builder.get_object('save').connect('activate', self.on_save_activate)
+        builder.get_object('stop').connect('activate', self.on_stop_activate)
+        builder.get_object('recent').connect('item-activated',
+            self.on_recent_item_activated)
+        builder.get_object('undo').connect('activate', self.on_undo_activate)
+        builder.get_object('quit').connect('activate', self.on_quit_activate)
+        builder.get_object('copy').connect('activate', self.on_copy_activate)
+        builder.get_object('paste').connect('activate', self.on_paste_activate)
+        builder.get_object('preferences').connect('activate',
+            self.on_preferences_activate)
+        builder.get_object('about').connect('activate', self.on_about_activate)
+        toolbar_action.connect('toggled', self.on_toolbar_toggled)
+        self.aboutwindow.connect('response', lambda x, *args: x.hide())
+        self.aboutwindow.connect('delete-event',
+            lambda x, *args: x.hide_on_delete())
+        self.window.connect('delete-event', self.on_window_delete_event)
+        self.prefswindow.connect('response', lambda x, *args: x.hide())
+        self.prefswindow.connect('delete-event',
+            lambda x, *args: x.hide_on_delete())
+        # FIXME Delete to here when above bug is fixed
+
+        self.glk.connect('notify::program-name', self.change_window_title)
+        self.glk.connect('notify::story-name', self.change_window_title)
+
+        # Create preferences window
+        # TODO
+
+    def change_window_title(self, glk, pspec, data=None):
+        if glk.props.program_name is None:
+            title = "Chimara"
+        elif glk.props.story_name is None:
+            title = "{interp} - Chimara".format(interp=glk.props.program_name)
+        else:
+            title = "{interp} - {story} - Chimara".format(
+                interp=glk.props.program_name,
+                story=glk.props.story_name)
+        self.window.props.title = title
+
+    def on_open_activate(self, action, data=None):
+        if not self.confirm_open_new_game():
+            return
+
+        dialog = Gtk.FileChooserDialog(_('Open Game'), self.window,
+            Gtk.FileChooserAction.OPEN,
+            (Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL,
+            Gtk.STOCK_OPEN, Gtk.ResponseType.ACCEPT))
+
+        # Get last opened path
+        path = _maybe(self.state_settings.get_value('last-open-path'))
+        if path is not None:
+            dialog.set_current_folder(path)
+
+        response = dialog.run()
+        dialog.hide()
+        if response != Gtk.ResponseType.ACCEPT:
+            return
+
+        gamefile = dialog.get_file()
+        self.search_for_graphics_file(gamefile.get_path())
+        try:
+            self.glk.run_game_file(gamefile)
+        except GLib.Error as e:
+            error_dialog(self.window, _('Could not open game file {filename}: {errmsg}').format(filename=gamefile.get_path(), errmsg=e.message))
+            return
+
+        path = dialog.get_current_folder()
+        if path is not None:
+            self.state_settings.last_open_path = path
+
+        # Add file to recent files list
+        manager = Gtk.RecentManager.get_default()
+        uri = gamefile.get_uri()
+        manager.add_item(uri)
+
+        dialog.destroy()
+
+    def on_recent_item_activated(self, chooser, data=None):
+        if not self.confirm_open_new_game():
+            return
+
+        uri = chooser.get_current_uri()
+        gamefile = Gio.file_new_for_uri(uri)
+
+        self.search_for_graphics_file(gamefile.get_path())
+        try:
+            self.glk.run_game_file(gamefile)
+        except GLib.Error as e:
+            error_dialog(self.window,
+                _('Could not open game file {filename}: {errmsg}').format(
+                    filename=gamefile.get_basename(),
+                    errmsg=e.message))
+            return
+
+        # Add file to recent files list again, this updates it to most recently
+        # used
+        manager = Gtk.RecentManager.get_default()
+        manager.add_item(uri)
+
+    def on_stop_activate(self, action, data=None):
+        self.glk.stop()
+
+    def on_quit_chimara_activate(self, action, data=None):
+        Gtk.main_quit()
+
+    def on_copy_activate(self, action, data=None):
+        focus = self.window.get_focus()
+        # Call "copy clipboard" on any widget that defines it
+        if (isinstance(focus, Gtk.Label)
+            or isinstance(focus, Gtk.Entry)
+            or isinstance(focus, Gtk.TextView)):
+            focus.emit('copy-clipboard')
+
+    def on_paste_activate(self, action, data=None):
+        focus = self.window.get_focus()
+        # Call "paste clipboard" on any widget that defines it
+        if isinstance(focus, Gtk.Entry) or isinstance(focus, Gtk.TextView):
+            focus.emit('paste-clipboard')
+
+    def on_preferences_activate(self, action, data=None):
+        self.prefswindow.present()
+
+    def on_toolbar_toggled(self, action, data=None):
+        if action.get_active():
+            self.toolbar.show()
+        else:
+            self.toolbar.hide()
+
+    def on_undo_activate(self, action, data=None):
+        self.glk.feed_line_input('undo')
+
+    def on_save_activate(self, action, data=None):
+        self.glk.feed_line_input('save')
+
+    def on_restore_activate(self, action, data=None):
+        self.glk.feed_line_input('restore')
+
+    def on_restart_activate(self, action, data=None):
+        self.glk.feed_line_input('restart')
+
+    def on_quit_activate(self, action, data=None):
+        self.glk.feed_line_input('quit')
+
+    def on_about_activate(self, action, data=None):
+        self.aboutwindow.set_version(config.PACKAGE_VERSION)
+        self.aboutwindow.present()
+
+    def on_window_delete_event(self, widget, event, data=None):
+        Gtk.main_quit()
+        return True
+
+    def confirm_open_new_game(self):
+        """
+        If a game is running in the Glk widget, warn the user that they will
+        quit the currently running game if they open a new one. Returns True if
+        no game was running. Returns False if the user cancelled. Returns True
+        and shuts down the running game if the user wishes to continue.
+        """
+        if not self.glk.props.running:
+            return True
+
+        dialog = Gtk.MessageDialog(self.window,
+            Gtk.DialogFlags.MODAL | Gtk.DialogFlags.DESTROY_WITH_PARENT,
+            Gtk.MessageType.WARNING, Gtk.ButtonsType.CANCEL,
+            _("Are you sure you want to open a new game?"))
+        dialog.format_secondary_text(
+            _("If you open a new game, you will quit the one you are "
+            "currently playing."))
+        dialog.add_button(Gtk.STOCK_OPEN, Gtk.ResponseType.OK)
+        response = dialog.run()
+        dialog.hide()
+
+        if response != Gtk.ResponseType.OK:
+            return False
+
+        self.glk.stop()
+        self.glk.wait()
+        return True
+
+    def search_for_graphics_file(self, filename):
+        """Internal function: See if there is a corresponding graphics file"""
+
+        # First get the name of the story file
+        base = os.path.basename(filename)
+        base_noext = os.path.splitext(base)[0]
+
+        # Check in the stored resource path, if set
+        resource_path = _maybe(self.prefs_settings.get_value('resource-path'))
+
+        # Otherwise check in the current directory
+        if resource_path is None:
+            resource_path = os.path.dirname(filename)
+
+        blorbfile = os.path.join(resource_path, base_noext + '.blb')
+        if os.path.exists(blorbfile):
+            self.glk.graphics_file = blorbfile
+
+
+def _maybe(variant):
+    """Gets a maybe value from a GVariant - not handled in PyGI"""
+    v = variant.get_maybe()
+    if v is None:
+        return None
+    return v.unpack()
+
+
+def error_dialog(parent, message):
+    dialog = Gtk.MessageDialog(parent, Gtk.DialogFlags.DESTROY_WITH_PARENT,
+        Gtk.MessageType.ERROR, Gtk.ButtonsType.OK, message)
+    dialog.run()
+    dialog.destroy()
+
+if __name__ == '__main__':
+    Gdk.threads_init()
+
+    player = Player()
+    player.window.show_all()
+
+    if len(sys.argv) == 3:
+        player.glk.props.graphics_file = sys.argv[2]
+    if len(sys.argv) >= 2:
+        try:
+            player.glk.run_game(sys.argv[1])
+        except GLib.Error as e:
+            error_dialog(player.window,
+                _("Error starting Glk library: {errmsg}").format(
+                    errmsg=e.message))
+            sys.exit(1)
+
+    Gdk.threads_enter()
+    Gtk.main()
+    Gdk.threads_leave()
+
+    player.glk.stop()
+    player.glk.wait()
+
+    sys.exit(0)
diff --git a/tests/chinesedicttest.ulx b/tests/chinesedicttest.ulx
new file mode 100644 (file)
index 0000000..f58c631
Binary files /dev/null and b/tests/chinesedicttest.ulx differ
diff --git a/tests/dictflagtest.ulx b/tests/dictflagtest.ulx
new file mode 100644 (file)
index 0000000..2371b34
Binary files /dev/null and b/tests/dictflagtest.ulx differ
diff --git a/tests/dictflagtest.z5 b/tests/dictflagtest.z5
new file mode 100644 (file)
index 0000000..dcdc6a0
Binary files /dev/null and b/tests/dictflagtest.z5 differ
index 7f0b40e98a4109ce7429cee79bf6e7480000d3c7..91f66cec99c6fba127d4b56b3a38dc8f5d65c9ad 100644 (file)
Binary files a/tests/externalfile.ulx and b/tests/externalfile.ulx differ
index 264de510b067ebc6b772ce904dad126bfb5e5502..f3815a0a1ea87513a14406d275d8c0dbc748d079 100644 (file)
@@ -1,6 +1,6 @@
 <?xml version="1.0"?>
 <interface>
-  <!-- interface-requires gtk+ 2.12 -->
+  <requires lib="gtk+" version="2.16"/>
   <!-- interface-naming-policy project-wide -->
   <object class="GtkWindow" id="window">
     <property name="border_width">6</property>
       <row>
         <col id="0" translatable="yes">ArrayLimitTest</col>
         <col id="1">arraylimittest.ulx</col>
+      </row>
+      <row>
+        <col id="0" translatable="yes">ChineseDictTest</col>
+        <col id="1">chinesedicttest.ulx</col>
       </row>
          <row>
         <col id="0" translatable="yes">DateTimeTest</col>
         <col id="1">datetimetest.ulx</col>
       </row>
+      <row>
+        <col id="0" translatable="yes">DictFlagTest</col>
+        <col id="1">dictflagtest.ulx</col>
+      </row>
+      <row>
+        <col id="0" translatable="yes">DictFlagTest (Z5)</col>
+        <col id="1">dictflagtest.z5</col>
+      </row>
       <row>
         <col id="0" translatable="yes">ExternalFile</col>
         <col id="1">externalfile.ulx</col>
         <col id="0" translatable="yes">MemStreamTest</col>
         <col id="1">memstreamtest.ulx</col>
       </row>
+      <row>
+        <col id="0" translatable="yes">ResStreamTest</col>
+        <col id="1">resstreamtest.gblorb</col>
+      </row>
       <row>
         <col id="0" translatable="yes">UniCaseTest</col>
         <col id="1">unicasetest.ulx</col>
         <col id="0" translatable="yes">UniDictTest</col>
         <col id="1">unidicttest.ulx</col>
       </row>
+      <row>
+        <col id="0" translatable="yes">UniSourceTest</col>
+        <col id="1">unisourcetest.ulx</col>
+      </row>
       <row>
         <col id="0" translatable="yes">WindowTest</col>
         <col id="1">windowtest.ulx</col>
diff --git a/tests/resstreamtest.gblorb b/tests/resstreamtest.gblorb
new file mode 100644 (file)
index 0000000..123c0ac
Binary files /dev/null and b/tests/resstreamtest.gblorb differ
index e89dcf32d0a12d372b5b115eefeaa3694542f597..5540624ad564da77b58481b81af23cbdb9cbe6a0 100644 (file)
@@ -3,26 +3,24 @@
 #include <unistd.h>
 #include <string.h>
 
+#define NUM_CHANNELS 2
+
 void
 glk_main(void)
 {
-       if(!glk_gestalt(gestalt_Sound, 0)) {
+       if(!glk_gestalt(gestalt_Sound2, 0)) {
                fprintf(stderr, "Sound not supported.\n");
                return;
        }
-       if(!glk_gestalt(gestalt_SoundVolume, 0)) {
-               fprintf(stderr, "Sound volume not supported.\n");
-               return;
-       }
-       if(!glk_gestalt(gestalt_SoundNotify, 0)) {
-               fprintf(stderr, "Sound notification not supported.\n");
-               return;
-       }
        
-       schanid_t sc = glk_schannel_create(0);
-       if(!sc) {
-               fprintf(stderr, "Could not create sound channel.\n");
-               return;
+       schanid_t sc[NUM_CHANNELS];
+       int count;
+       for(count = 0; count < NUM_CHANNELS; count++) {
+               sc[count] = glk_schannel_create(count);
+               if(!sc[count]) {
+                       fprintf(stderr, "Could not create sound channel number %d.\n", count);
+                       return;
+               }
        }
 
        /* Open the main window. */
@@ -35,12 +33,16 @@ glk_main(void)
        glk_set_window(mainwin);
        glk_put_string("Copy a sound file to the current directory and rename it "
            "to SND3. Supported formats: AIFF, OGG, MOD, S3M, IT, XM. Type 'play' "
-           "to play it.\n");
+           "to play it.\n\n"
+               "If you want to test multi-sound playing, copy another sound file and "
+               "rename it to SND4 as well. You can't stop it, so make it a short "
+               "sound effect.\n");
 
        char buffer[1024];
        int len;
        int finish = 0;
        int repeat = 1;
+       int ramp = 0;
 
        event_t ev;
        while(!finish) {
@@ -67,13 +69,13 @@ glk_main(void)
                                        finish = 1;
                                } else if(strcmp(buffer, "play") == 0) {
                                        glk_put_string("Playing sound.\n");
-                                       if(!glk_schannel_play_ext(sc, 3, repeat, 1)) {
+                                       if(!glk_schannel_play_ext(sc[0], 3, repeat, 1)) {
                                                fprintf(stderr, "Could not start sound channel.\n");
                                                finish = 1;
                                        }
                                } else if(strcmp(buffer, "stop") == 0) {
                                        glk_put_string("Stopping sound.\n");
-                                       glk_schannel_stop(sc);
+                                       glk_schannel_stop(sc[0]);
                                } else if(strcmp(buffer, "repeat") == 0) {
                                        glk_put_string("Setting repeat to ");
                                        if(repeat == 1) {
@@ -89,19 +91,49 @@ glk_main(void)
                                                glk_put_string("ONCE.\n");
                                                repeat = 1;
                                        }
+                               } else if(strcmp(buffer, "pause") == 0) {
+                                       glk_put_string("Pausing channel.\n");
+                                       glk_schannel_pause(sc[0]);
+                               } else if(strcmp(buffer, "unpause") == 0) {
+                                       glk_put_string("Unpausing channel.\n");
+                                       glk_schannel_unpause(sc[0]);
+                               } else if(strcmp(buffer, "ramp") == 0) {
+                                       glk_put_string("Ramping volume to ");
+                                       if(ramp == 0) {
+                                               glk_put_string("HALF.\n");
+                                               glk_schannel_set_volume_ext(sc[0], 0x8000, 3000, 42);
+                                               ramp = 1;
+                                       } else if(ramp == 1) {
+                                               glk_put_string("FULL.\n");
+                                               glk_schannel_set_volume_ext(sc[0], 0x10000, 3000, 69);
+                                               ramp = 0;
+                                       }
+                               } else if(strcmp(buffer, "multi") == 0) {
+                                       glk_put_string("Playing two sounds. (These will not repeat.)\n");
+                                       glui32 sounds[NUM_CHANNELS] = { 3, 4 };
+                                       if(glk_schannel_play_multi(sc, NUM_CHANNELS, sounds, NUM_CHANNELS, 1) < 2) {
+                                               fprintf(stderr, "Tried to start %d sounds, but not all were successful.", NUM_CHANNELS);
+                                               finish = 1;
+                                       }
                                } else if(strcmp(buffer, "help") == 0) {
-                                       glk_put_string("Type PLAY or REPEAT or STOP or QUIT.\n");
+                                       glk_put_string("Type PLAY or MULTI or REPEAT or PAUSE or UNPAUSE or RAMP or STOP or QUIT.\n");
                                }
                                break;
                        case evtype_SoundNotify:
                                glk_cancel_line_event(mainwin, NULL);
                                glk_put_string("\nGot sound notify event!\n");
                                break;
+                       case evtype_VolumeNotify:
+                               glk_cancel_line_event(mainwin, NULL);
+                               glk_put_string("\nGot volume notify event!\n");
+                               break;
                        default:
                                ;
                }
        }
 
-       glk_schannel_stop(sc);
-       glk_schannel_destroy(sc);
+       for(count = 0; count < NUM_CHANNELS; count++) {
+               glk_schannel_stop(sc[count]);
+               glk_schannel_destroy(sc[count]);
+       }
 }
diff --git a/tests/unisourcetest.ulx b/tests/unisourcetest.ulx
new file mode 100644 (file)
index 0000000..5b520d7
Binary files /dev/null and b/tests/unisourcetest.ulx differ