* Added timer support
authorMarijn van Vliet <marijn.vanvliet@med.kuleuven.be>
Fri, 22 May 2009 08:03:47 +0000 (08:03 +0000)
committerMarijn van Vliet <marijn.vanvliet@med.kuleuven.be>
Fri, 22 May 2009 08:03:47 +0000 (08:03 +0000)
* Added multiwin.c test case (which still segfaults all the time)

git-svn-id: http://lassie.dyndns-server.com/svn/gargoyle-gtk@60 ddfedd41-794f-dd11-ae45-00112f111e67

src/Makefile.am
src/chimara-glk-private.h
src/chimara-glk.c
src/gestalt.c
src/input.c
src/main.c
src/multiwin.c [new file with mode: 0644]
src/timer.c [new file with mode: 0644]
src/timer.h [new file with mode: 0644]

index ace6c6bd1dcfea84ac1c5dd5cef5a0cda80880ed..6d1ed53e61547a84616d7357e706ec881ccbcede 100644 (file)
@@ -30,6 +30,7 @@ libchimara_la_SOURCES = \
        stream.c stream.h \
        strio.c \
        style.c \
+       timer.c timer.h \
        window.c window.h 
 libchimara_la_CPPFLAGS = \
        -DG_LOG_DOMAIN=\"Chimara\"
@@ -39,7 +40,7 @@ libchimara_la_LDFLAGS = -no-undefined -export-symbols-regex "^(chimara_)?glk_"
 libchimara_includedir = $(includedir)/chimara/chimara
 libchimara_include_HEADERS = chimara-glk.h
 
-pkglib_LTLIBRARIES = first.la model.la gridtest.la splittest.la
+pkglib_LTLIBRARIES = first.la model.la gridtest.la splittest.la multiwin.la
 PLUGIN_LIBTOOL_FLAGS=-module -avoid-version -export-symbols-regex "^glk_main$$"
 
 first_la_SOURCES = first.c
@@ -54,6 +55,10 @@ gridtest_la_LDFLAGS = $(PLUGIN_LIBTOOL_FLAGS)
 splittest_la_SOURCES = splittest.c
 splittest_la_LDFLAGS = $(PLUGIN_LIBTOOL_FLAGS)
 
+multiwin_la_SOURCES = multiwin.c
+multiwin_la_CFLAGS = @GLIB_CFLAGS@
+multiwin_la_LDFLAGS = $(PLUGIN_LIBTOOL_FLAGS)
+
 CLEANFILES = chimara.ui
 
 chimara.ui: chimara.glade
index 2a1373d7459fb54e2fcbc68285237a7a9800071d..35fac923f73086a5d83923676b75480a1950c00c 100644 (file)
@@ -46,6 +46,8 @@ struct _ChimaraGlkPrivate {
     strid_t current_stream;
     /* List of streams currently in existence */
     GList *stream_list;
+       /* Current timer */
+       guint timer_id;
 };
 
 #define CHIMARA_GLK_PRIVATE(obj) \
index 7ad1f81ef2179e999aaa40cb83b2da202039d879..14d1e44801f7b73e0c76c41bdded91eeeb7d0c3f 100644 (file)
@@ -86,6 +86,7 @@ chimara_glk_init(ChimaraGlk *self)
     priv->fileref_list = NULL;
     priv->current_stream = NULL;
     priv->stream_list = NULL;
+       priv->timer_id = 0;
 }
 
 static void
index 5add1c3ba1dd4bbff7eb8403b9ddfe92c2f99308..398aede093e5839e12fbeb1d6001dde22bddb706 100644 (file)
@@ -99,10 +99,13 @@ glk_gestalt_ext(glui32 sel, glui32 val, glui32 *arr, glui32 arrlen)
                /* Unicode capabilities present */
                case gestalt_Unicode:
                        return 1;
+
+               /* Timer capabilities present */
+               case gestalt_Timer:
+                       return 1;
                        
                /* Unsupported capabilities */
                case gestalt_MouseInput:
-               case gestalt_Timer:
                case gestalt_Graphics:
                case gestalt_DrawImage:
                case gestalt_Sound:
index c5134192dfeb974489fc034e96be9ecea9aaf264..e55ed23bb721fe91bf44d448de14bbd46fcaa55f 100644 (file)
@@ -41,6 +41,21 @@ glk_request_char_event_uni(winid_t win)
        g_signal_handler_unblock( G_OBJECT(win->widget), win->keypress_handler );
 }
 
+/**
+ * glk_cancel_char_event:
+ * @win: A window to cancel the latest char event request on.
+ *
+ * Cancels the last char event request on the given window.
+ */
+void
+glk_cancel_char_event(winid_t win)
+{
+       /* TODO: write me */
+       VALID_WINDOW(win, return);
+       g_return_if_fail(win->input_request_type == INPUT_REQUEST_NONE);
+       g_return_if_fail(win->type != wintype_TextBuffer || win->type != wintype_TextGrid);
+}
+
 /* Internal function: Request either latin-1 or unicode line input, in a text grid window. */
 static void
 text_grid_request_line_event_common(winid_t win, glui32 maxlen, gboolean insert, gchar *inserttext)
@@ -235,6 +250,33 @@ glk_request_line_event_uni(winid_t win, glui32 *buf, glui32 maxlen, glui32 initl
        g_free(utf8);
 }
 
+/**
+ * glk_cancel_line_event:
+ * @win: A text buffer or text grid window to cancel line input on.
+ * @event: Will be filled in if the user had already input something.
+ *
+ * This cancels a pending request for line input. (Either Latin-1 or Unicode.)
+ *
+ * The event pointed to by the event argument will be filled in as if the
+ * player had hit enter, and the input composed so far will be stored in the
+ * buffer; see below. If you do not care about this information, pass NULL as
+ * the event argument. (The buffer will still be filled.) 
+ *
+ * For convenience, it is legal to call glk_cancel_line_event() even if there
+ * is no line input request on that window. The event type will be set to
+ * evtype_None in this case.
+ */
+void
+glk_cancel_line_event(winid_t win, event_t *event)
+{
+       /* TODO: write me */
+       VALID_WINDOW(win, return);
+       g_return_if_fail(win->input_request_type == INPUT_REQUEST_NONE);
+       g_return_if_fail(win->type != wintype_TextBuffer || win->type != wintype_TextGrid);
+
+       event = NULL;
+}
+
 /* Internal function: General callback for signal key-press-event on a text buffer or text grid window. Used in character input on both text buffers and grids, and also in line input on grids, to redirect keystrokes to the line input field. Blocked when not in use. */
 gboolean
 on_window_key_press_event(GtkWidget *widget, GdkEventKey *event, winid_t win)
index c34e4c5f8883a1dd548041257d11f0c6b6d365d0..f6c312e04c5fc090374201ac0374310cde686c55 100644 (file)
@@ -118,7 +118,7 @@ main(int argc, char *argv[])
 
        g_object_unref( G_OBJECT(builder) );
 
-    if( !chimara_glk_run(CHIMARA_GLK(glk), ".libs/first.so", &error) ) {
+    if( !chimara_glk_run(CHIMARA_GLK(glk), ".libs/multiwin.so", &error) ) {
         error_dialog(GTK_WINDOW(window), error, "Error starting Glk library: ");
         return 1;
     }
diff --git a/src/multiwin.c b/src/multiwin.c
new file mode 100644 (file)
index 0000000..261d7fb
--- /dev/null
@@ -0,0 +1,843 @@
+#include "glk.h"
+
+/* multiwin.c: Sample program for Glk API, version 0.5.
+    Designed by Andrew Plotkin <erkyrath@eblong.com>
+    http://www.eblong.com/zarf/glk/index.html
+    This program is in the public domain.
+*/
+
+/* This example demonstrates multiple windows and timed input in the
+    Glk API. */
+
+/* This is the cleanest possible form of a Glk program. It includes only
+    "glk.h", and doesn't call any functions outside Glk at all. We even
+    define our own string functions, rather than relying on the
+    standard libraries. */
+
+/* We also define our own TRUE and FALSE and NULL. */
+#ifndef TRUE
+#define TRUE 1
+#endif
+#ifndef FALSE
+#define FALSE 0
+#endif
+#ifndef NULL
+#define NULL 0
+#endif
+
+/* The story and status windows. */
+static winid_t mainwin1 = NULL;
+static winid_t mainwin2 = NULL;
+static winid_t statuswin = NULL;
+
+/* Key windows don't get stored in a global variable; we'll find them
+    by iterating over the list and looking for this rock value. */
+#define KEYWINROCK (97)
+
+/* For the two main windows, we keep a flag saying whether that window
+    has a line input request pending. (Because if it does, we need to
+    cancel the line input before printing to that window.) */
+static int inputpending1, inputpending2;
+/* When we cancel line input, we should remember how many characters
+    had been typed. This lets us restart the input with those characters
+    already in place. */
+static int already1, already2;
+
+/* There's a three-second timer which can be on or off. */
+static int timer_on = FALSE;
+
+/* Forward declarations */
+void glk_main(void);
+
+static void draw_statuswin(void);
+static void draw_keywins(void);
+static void perform_key(winid_t win, glui32 key);
+static void perform_timer(void);
+
+static int str_eq(char *s1, char *s2);
+static int str_len(char *s1);
+static char *str_cpy(char *s1, char *s2);
+static char *str_cat(char *s1, char *s2);
+static void num_to_str(char *buf, int num);
+
+static void verb_help(winid_t win);
+static void verb_jump(winid_t win);
+static void verb_yada(winid_t win);
+static void verb_both(winid_t win);
+static void verb_clear(winid_t win);
+static void verb_page(winid_t win);
+static void verb_pageboth(winid_t win);
+static void verb_timer(winid_t win);
+static void verb_untimer(winid_t win);
+static void verb_chars(winid_t win);
+static void verb_quit(winid_t win);
+
+/* The glk_main() function is called by the Glk system; it's the main entry
+    point for your program. */
+void glk_main(void)
+{
+    char commandbuf1[256]; /* For mainwin1 */
+    char commandbuf2[256]; /* For mainwin2 */
+
+    /* Open the main windows. */
+    mainwin1 = glk_window_open(0, 0, 0, wintype_TextBuffer, 1);
+    if (!mainwin1) {
+        /* It's possible that the main window failed to open. There's
+            nothing we can do without it, so exit. */
+        return; 
+    }
+    
+    /* Open a second window: a text grid, above the main window, five 
+        lines high. It is possible that this will fail also, but we accept 
+        that. */
+    statuswin = glk_window_open(mainwin1, 
+        winmethod_Above | winmethod_Fixed, 
+        5, wintype_TextGrid, 0);
+        
+    /* And a third window, a second story window below the main one. */
+    mainwin2 = glk_window_open(mainwin1, 
+        winmethod_Below | winmethod_Proportional, 
+        50, wintype_TextBuffer, 0);
+        
+    /* We're going to be switching from one window to another all the
+        time. So we'll be setting the output stream on a case-by-case
+        basis. Every function that prints must set the output stream
+        first. (Contrast model.c, where the output stream is always the
+        main window, and every function that changes that must set it
+        back afterwards.) */
+    
+    glk_set_window(mainwin1);
+    glk_put_string("Multiwin\nAn Interactive Sample Glk Program\n");
+    glk_put_string("By Andrew Plotkin.\nRelease 3.\n");
+    glk_put_string("Type \"help\" for a list of commands.\n");
+    
+    glk_set_window(mainwin2);
+    glk_put_string("Note that the upper left-hand window accepts character");
+    glk_put_string(" input. Hit 'h' to split the window horizontally, 'v' to");
+    glk_put_string(" split the window vertically, 'c' to close a window,");
+    glk_put_string(" and any other key (including special keys) to display");
+    glk_put_string(" key codes. All new windows accept these same keys as");
+    glk_put_string(" well.\n\n");
+    glk_put_string("This bottom window accepts normal line input.\n");
+    
+    if (statuswin) {
+        /* For fun, let's open a fourth window now, splitting the status
+            window. */
+        winid_t keywin;
+        keywin = glk_window_open(statuswin, 
+            winmethod_Left | winmethod_Proportional, 
+            66, wintype_TextGrid, KEYWINROCK);
+        if (keywin) {
+            glk_request_char_event(keywin);
+        }
+    }
+    
+    /* Draw the key window now, since we don't draw it every input (as
+        we do the status window. */
+    draw_keywins();
+
+    inputpending1 = FALSE;
+    inputpending2 = FALSE;
+    already1 = 0;
+    already2 = 0;
+    
+    while (1) {
+        char *cx, *cmd;
+        int doneloop, len;
+        winid_t whichwin;
+        event_t ev;
+        
+        draw_statuswin();
+        /* We're not redrawing the key windows every command. */
+        
+        /* Either main window, or both, could already have line input
+            pending. If so, leave that window alone. If there is no
+            input pending on a window, set a line input request, but
+            keep around any characters that were in the buffer already. */
+        
+        if (mainwin1 && !inputpending1) {
+            glk_set_window(mainwin1);
+            glk_put_string("\n>");
+            /* We request up to 255 characters. The buffer can hold 256, 
+                but we are going to stick a null character at the end, so 
+                we have to leave room for that. Note that the Glk library 
+                does *not* put on that null character. */
+            glk_request_line_event(mainwin1, commandbuf1, 255, already1);
+            inputpending1 = TRUE;
+        }
+        
+        if (mainwin2 && !inputpending2) {
+            glk_set_window(mainwin2);
+            glk_put_string("\n>");
+            /* See above. */
+            glk_request_line_event(mainwin2, commandbuf2, 255, already2);
+            inputpending2 = TRUE;
+        }
+        
+        doneloop = FALSE;
+        while (!doneloop) {
+        
+            /* Grab an event. */
+            glk_select(&ev);
+            
+            switch (ev.type) {
+            
+                case evtype_LineInput:
+                    /* If the event comes from one main window or the other,
+                        we mark that window as no longer having line input
+                        pending. We also set commandbuf to point to the
+                        appropriate buffer. Then we leave the event loop. */
+                    if (mainwin1 && ev.win == mainwin1) {
+                        whichwin = mainwin1;
+                        inputpending1 = FALSE;
+                        cmd = commandbuf1;
+                        doneloop = TRUE;
+                    }
+                    else if (mainwin2 && ev.win == mainwin2) {
+                        whichwin = mainwin2;
+                        inputpending2 = FALSE;
+                        cmd = commandbuf2;
+                        doneloop = TRUE;
+                    }
+                    break;
+                    
+                case evtype_CharInput:
+                    /* It's a key event, from one of the keywins. We
+                        call a subroutine rather than exiting the
+                        event loop (although I could have done it
+                        that way too.) */
+                    perform_key(ev.win, ev.val1);
+                    break;
+                
+                case evtype_Timer:
+                    /* It's a timer event. This does exit from the event
+                        loop, since we're going to interrupt input in
+                        mainwin1 and then re-print the prompt. */
+                    whichwin = NULL;
+                    cmd = NULL; 
+                    doneloop = TRUE;
+                    break;
+                    
+                case evtype_Arrange:
+                    /* Windows have changed size, so we have to redraw the
+                        status window and key window. But we stay in the
+                        event loop. */
+                    draw_statuswin();
+                    draw_keywins();
+                    break;
+            }
+        }
+        
+        if (cmd == NULL) {
+            /* It was a timer event. */
+            perform_timer();
+            continue;
+        }
+        
+        /* It was a line input event. cmd now points at a line of input
+            from one of the main windows. */
+        
+        /* The line we have received in commandbuf is not null-terminated.
+            We handle that first. */
+        len = ev.val1; /* Will be between 0 and 255, inclusive. */
+        cmd[len] = '\0';
+        
+        /* Then squash to lower-case. */
+        for (cx = cmd; *cx; cx++) { 
+            *cx = glk_char_to_lower(*cx);
+        }
+        
+        /* Then trim whitespace before and after. */
+        
+        for (cx = cmd; *cx == ' '; cx++, len--) { };
+        
+        cmd = cx;
+        
+        for (cx = cmd+len-1; cx >= cmd && *cx == ' '; cx--) { };
+        *(cx+1) = '\0';
+        
+        /* cmd now points to a nice null-terminated string. We'll do the
+            simplest possible parsing. */
+        if (str_eq(cmd, "")) {
+            glk_set_window(whichwin);
+            glk_put_string("Excuse me?\n");
+        }
+        else if (str_eq(cmd, "help")) {
+            verb_help(whichwin);
+        }
+        else if (str_eq(cmd, "yada")) {
+            verb_yada(whichwin);
+        }
+        else if (str_eq(cmd, "both")) {
+            verb_both(whichwin);
+        }
+        else if (str_eq(cmd, "clear")) {
+            verb_clear(whichwin);
+        }
+        else if (str_eq(cmd, "page")) {
+            verb_page(whichwin);
+        }
+        else if (str_eq(cmd, "pageboth")) {
+            verb_pageboth(whichwin);
+        }
+        else if (str_eq(cmd, "timer")) {
+            verb_timer(whichwin);
+        }
+        else if (str_eq(cmd, "untimer")) {
+            verb_untimer(whichwin);
+        }
+        else if (str_eq(cmd, "chars")) {
+            verb_chars(whichwin);
+        }
+        else if (str_eq(cmd, "jump")) {
+            verb_jump(whichwin);
+        }
+        else if (str_eq(cmd, "quit")) {
+            verb_quit(whichwin);
+        }
+        else {
+            glk_set_window(whichwin);
+            glk_put_string("I don't understand the command \"");
+            glk_put_string(cmd);
+            glk_put_string("\".\n");
+        }
+        
+        if (whichwin == mainwin1)
+            already1 = 0;
+        else if (whichwin == mainwin2)
+            already2 = 0;
+    }
+}
+
+static void draw_statuswin(void)
+{
+    glui32 width, height;
+    
+    if (!statuswin) {
+        /* It is possible that the window was not successfully 
+            created. If that's the case, don't try to draw it. */
+        return;
+    }
+    
+    glk_set_window(statuswin);
+    glk_window_clear(statuswin);
+    
+    glk_window_get_size(statuswin, &width, &height);
+    
+    /* Draw a decorative compass rose in the center. */
+    width = (width/2);
+    if (width > 0)
+        width--;
+    height = (height/2);
+    if (height > 0)
+        height--;
+        
+    glk_window_move_cursor(statuswin, width, height+0);
+    glk_put_string("\\|/");
+    glk_window_move_cursor(statuswin, width, height+1);
+    glk_put_string("-*-");
+    glk_window_move_cursor(statuswin, width, height+2);
+    glk_put_string("/|\\");
+    
+}
+
+/* This draws some corner decorations in *every* key window -- the
+    one created at startup, and any later ones. It finds them all
+    with glk_window_iterate. */
+static void draw_keywins(void)
+{
+    winid_t win;
+    glui32 rock;
+    glui32 width, height;
+    
+    for (win = glk_window_iterate(NULL, &rock);
+            win;
+            win = glk_window_iterate(win, &rock)) {
+        if (rock == KEYWINROCK) {
+            glk_set_window(win);
+            glk_window_clear(win);
+            glk_window_get_size(win, &width, &height);
+            glk_window_move_cursor(win, 0, 0);
+            glk_put_char('O');
+            glk_window_move_cursor(win, width-1, 0);
+            glk_put_char('O');
+            glk_window_move_cursor(win, 0, height-1);
+            glk_put_char('O');
+            glk_window_move_cursor(win, width-1, height-1);
+            glk_put_char('O');
+        }
+    }
+}
+
+/* React to character input in a key window. */
+static void perform_key(winid_t win, glui32 key)
+{
+    glui32 width, height, len;
+    int ix;
+    char buf[128], keyname[64];
+    
+    if (key == 'h' || key == 'v') {
+        winid_t newwin;
+        glui32 loc;
+        /* Open a new keywindow. */
+        if (key == 'h')
+            loc = winmethod_Right | winmethod_Proportional;
+        else
+            loc = winmethod_Below | winmethod_Proportional;
+        newwin = glk_window_open(win, 
+            loc, 50, wintype_TextGrid, KEYWINROCK);
+        /* Since the new window has rock value KEYWINROCK, the
+            draw_keywins() routine will redraw it. */
+        if (newwin) {
+            /* Request character input. In this program, only keywins
+                get char input, so the CharInput events always call
+                perform_key() -- and so the new window will respond
+                to keys just as this one does. */
+            glk_request_char_event(newwin);
+            /* We now have to redraw the keywins, because any or all of
+                them could have changed size when we opened newwin.
+                glk_window_open() does not generate Arrange events; we
+                have to do the redrawing manually. */
+            draw_keywins();
+        }
+        /* Re-request character input for this window, so that future
+            keys are accepted. */
+        glk_request_char_event(win);
+        return;
+    }
+    else if (key == 'c') {
+        /* Close this keywindow. */
+        glk_window_close(win, NULL);
+        /* Again, any key windows could have changed size. Also the
+            status window could have (if this was the last key window). */
+        draw_keywins();
+        draw_statuswin();
+        return;
+    }
+    
+    /* Print a string naming the key that was just hit. */
+    
+    switch (key) {
+        case ' ':
+            str_cpy(keyname, "space");
+            break;
+        case keycode_Left:
+            str_cpy(keyname, "left");
+            break;
+        case keycode_Right:
+            str_cpy(keyname, "right");
+            break;
+        case keycode_Up:
+            str_cpy(keyname, "up");
+            break;
+        case keycode_Down:
+            str_cpy(keyname, "down");
+            break;
+        case keycode_Return:
+            str_cpy(keyname, "return");
+            break;
+        case keycode_Delete:
+            str_cpy(keyname, "delete");
+            break;
+        case keycode_Escape:
+            str_cpy(keyname, "escape");
+            break;
+        case keycode_Tab:
+            str_cpy(keyname, "tab");
+            break;
+        case keycode_PageUp:
+            str_cpy(keyname, "page up");
+            break;
+        case keycode_PageDown:
+            str_cpy(keyname, "page down");
+            break;
+        case keycode_Home:
+            str_cpy(keyname, "home");
+            break;
+        case keycode_End:
+            str_cpy(keyname, "end");
+            break;
+        default:
+            if (key >= keycode_Func1 && key < keycode_Func12) {
+                str_cpy(keyname, "function key");
+            }
+            else if (key < 32) {
+                str_cpy(keyname, "ctrl-");
+                keyname[5] = '@' + key;
+                keyname[6] = '\0';
+            }
+            else if (key <= 255) {
+                keyname[0] = key;
+                keyname[1] = '\0';
+            }
+            else {
+                str_cpy(keyname, "unknown key");
+            }
+            break;
+    }
+    
+    str_cpy(buf, "Key: ");
+    str_cat(buf, keyname);
+    
+    len = str_len(buf);
+    
+    /* Print the string centered in this window. */
+    glk_set_window(win);
+    glk_window_get_size(win, &width, &height);
+    glk_window_move_cursor(win, 0, height/2);
+    for (ix=0; ix<width; ix++)
+        glk_put_char(' ');
+        
+    width = width/2;
+    len = len/2;
+    
+    if (width > len)
+        width = width-len;
+    else
+        width = 0;
+    
+    glk_window_move_cursor(win, width, height/2);
+    glk_put_string(buf);
+    
+    /* Re-request character input for this window, so that future
+        keys are accepted. */
+    glk_request_char_event(win);
+}
+
+/* React to a timer event. This just prints "Tick" in mainwin1, but it
+    first has to cancel line input if any is pending. */
+static void perform_timer()
+{
+    event_t ev;
+    
+    if (!mainwin1)
+        return;
+    
+    if (inputpending1) {
+        glk_cancel_line_event(mainwin1, &ev);
+        if (ev.type == evtype_LineInput)
+            already1 = ev.val1;
+        inputpending1 = FALSE;
+    }
+
+    glk_set_window(mainwin1);
+    glk_put_string("Tick.\n");
+}
+
+/* This is a utility function. Given a main window, it finds the
+    "other" main window (if both actually exist) and cancels line
+    input in that other window (if input is pending.) It does not
+    set the output stream to point there, however. If there is only
+    one main window, this returns 0. */
+static winid_t print_to_otherwin(winid_t win)
+{
+    winid_t otherwin = NULL;
+    event_t ev;
+
+    if (win == mainwin1) {
+        if (mainwin2) {
+            otherwin = mainwin2;
+            glk_cancel_line_event(mainwin2, &ev);
+            if (ev.type == evtype_LineInput)
+                already2 = ev.val1;
+            inputpending2 = FALSE;
+        }
+    }
+    else if (win == mainwin2) {
+        if (mainwin1) {
+            otherwin = mainwin1;
+            glk_cancel_line_event(mainwin1, &ev);
+            if (ev.type == evtype_LineInput)
+                already1 = ev.val1;
+            inputpending1 = FALSE;
+        }
+    }
+    
+    return otherwin;
+}
+
+static void verb_help(winid_t win)
+{
+    glk_set_window(win);
+    
+    glk_put_string("This model only understands the following commands:\n");
+    glk_put_string("HELP: Display this list.\n");
+    glk_put_string("JUMP: Print a short message.\n");
+    glk_put_string("YADA: Print a long paragraph.\n");
+    glk_put_string("BOTH: Print a short message in both main windows.\n");
+    glk_put_string("CLEAR: Clear one window.\n");
+    glk_put_string("PAGE: Print thirty lines, demonstrating paging.\n");
+    glk_put_string("PAGEBOTH: Print thirty lines in each window.\n");
+    glk_put_string("TIMER: Turn on a timer, which ticks in the upper ");
+    glk_put_string("main window every three seconds.\n");
+    glk_put_string("UNTIMER: Turns off the timer.\n");
+    glk_put_string("CHARS: Prints the entire Latin-1 character set.\n");
+    glk_put_string("QUIT: Quit and exit.\n");
+}
+
+static void verb_jump(winid_t win)
+{
+    glk_set_window(win);
+    
+    glk_put_string("You jump on the fruit, spotlessly.\n");
+}
+
+/* Print some text in both windows. This uses print_to_otherwin() to
+    find the other window and prepare it for printing. */
+static void verb_both(winid_t win)
+{
+    winid_t otherwin;
+    
+    glk_set_window(win);
+    glk_put_string("Something happens in this window.\n");
+    
+    otherwin = print_to_otherwin(win);
+    
+    if (otherwin) {
+        glk_set_window(otherwin);
+        glk_put_string("Something happens in the other window.\n");
+    }
+}
+
+/* Clear a window. */
+static void verb_clear(winid_t win)
+{
+    glk_window_clear(win);
+}
+
+/* Print thirty lines. */
+static void verb_page(winid_t win)
+{
+    int ix;
+    char buf[32];
+    
+    glk_set_window(win);
+    for (ix=0; ix<30; ix++) {
+        num_to_str(buf, ix);
+        glk_put_string(buf);
+        glk_put_char('\n');
+    }
+}
+
+/* Print thirty lines in both windows. This gets fancy by printing
+    to each window alternately, without setting the output stream,
+    by using glk_put_string_stream() instead of glk_put_string(). 
+    There's no particular difference; this is just a demonstration. */
+static void verb_pageboth(winid_t win)
+{
+    int ix;
+    winid_t otherwin;
+    strid_t str, otherstr;
+    char buf[32];
+    
+    str = glk_window_get_stream(win);
+    otherwin = print_to_otherwin(win);
+    if (otherwin) 
+        otherstr = glk_window_get_stream(otherwin);
+    else
+        otherstr = NULL;
+
+    for (ix=0; ix<30; ix++) {
+        num_to_str(buf, ix);
+        str_cat(buf, "\n");
+        glk_put_string_stream(str, buf);
+        if (otherstr)
+            glk_put_string_stream(otherstr, buf);
+    }
+}
+
+/* Turn on the timer. The timer prints a tick in mainwin1 every three
+    seconds. */
+static void verb_timer(winid_t win)
+{
+    glk_set_window(win);
+    
+    if (timer_on) {
+        glk_put_string("The timer is already running.\n");
+        return;
+    }
+    
+    if (glk_gestalt(gestalt_Timer, 0) == 0) {
+        glk_put_string("Your Glk library does not support timer events.\n");
+        return;
+    }
+    
+    glk_put_string("A timer starts running in the upper window.\n");
+    glk_request_timer_events(3000); /* Every three seconds. */
+    timer_on = TRUE;
+}
+
+/* Turn off the timer. */
+static void verb_untimer(winid_t win)
+{
+    glk_set_window(win);
+    
+    if (!timer_on) {
+        glk_put_string("The timer is not currently running.\n");
+        return;
+    }
+    
+    glk_put_string("The timer stops running.\n");
+    glk_request_timer_events(0);
+    timer_on = FALSE;
+}
+
+/* Print every character, or rather try to. */
+static void verb_chars(winid_t win)
+{
+    int ix;
+    char buf[16];
+    
+    glk_set_window(win);
+    
+    for (ix=0; ix<256; ix++) {
+        num_to_str(buf, ix);
+        glk_put_string(buf);
+        glk_put_string(": ");
+        glk_put_char(ix);
+        glk_put_char('\n');
+    }
+}
+
+static void verb_yada(winid_t win)
+{
+    /* This is a goofy (and overly ornate) way to print a long paragraph. 
+        It just shows off line wrapping in the Glk implementation. */
+    #define NUMWORDS (13)
+    static char *wordcaplist[NUMWORDS] = {
+        "Ga", "Bo", "Wa", "Mu", "Bi", "Fo", "Za", "Mo", "Ra", "Po",
+            "Ha", "Ni", "Na"
+    };
+    static char *wordlist[NUMWORDS] = {
+        "figgle", "wob", "shim", "fleb", "moobosh", "fonk", "wabble",
+            "gazoon", "ting", "floo", "zonk", "loof", "lob",
+    };
+    static int wcount1 = 0;
+    static int wcount2 = 0;
+    static int wstep = 1;
+    static int jx = 0;
+    int ix;
+    int first = TRUE;
+    
+    glk_set_window(win);
+    
+    for (ix=0; ix<85; ix++) {
+        if (ix > 0) {
+            glk_put_string(" ");
+        }
+                
+        if (first) {
+            glk_put_string(wordcaplist[(ix / 17) % NUMWORDS]);
+            first = FALSE;
+        }
+        
+        glk_put_string(wordlist[jx]);
+        jx = (jx + wstep) % NUMWORDS;
+        wcount1++;
+        if (wcount1 >= NUMWORDS) {
+            wcount1 = 0;
+            wstep++;
+            wcount2++;
+            if (wcount2 >= NUMWORDS-2) {
+                wcount2 = 0;
+                wstep = 1;
+            }
+        }
+        
+        if ((ix % 17) == 16) {
+            glk_put_string(".");
+            first = TRUE;
+        }
+    }
+    
+    glk_put_char('\n');
+}
+
+static void verb_quit(winid_t win)
+{
+    glk_set_window(win);
+    
+    glk_put_string("Thanks for playing.\n");
+    glk_exit();
+    /* glk_exit() actually stops the process; it does not return. */
+}
+
+/* simple string length test */
+static int str_len(char *s1)
+{
+    int len;
+    for (len = 0; *s1; s1++)
+        len++;
+    return len;
+}
+
+/* simple string comparison test */
+static int str_eq(char *s1, char *s2)
+{
+    for (; *s1 && *s2; s1++, s2++) {
+        if (*s1 != *s2)
+            return FALSE;
+    }
+    
+    if (*s1 || *s2)
+        return FALSE;
+    else
+        return TRUE;
+}
+
+/* simple string copy */
+static char *str_cpy(char *s1, char *s2)
+{
+    char *orig = s1;
+    
+    for (; *s2; s1++, s2++)
+        *s1 = *s2;
+    *s1 = '\0';
+    
+    return orig;
+}
+
+/* simple string concatenate */
+static char *str_cat(char *s1, char *s2)
+{
+    char *orig = s1;
+    
+    while (*s1)
+        s1++;
+    for (; *s2; s1++, s2++)
+        *s1 = *s2;
+    *s1 = '\0';
+    
+    return orig;
+}
+
+/* simple number printer */
+static void num_to_str(char *buf, int num)
+{
+    int ix;
+    int size = 0;
+    char tmpc;
+    
+    if (num == 0) {
+        str_cpy(buf, "0");
+        return;
+    }
+    
+    if (num < 0) {
+        buf[0] = '-';
+        buf++;
+        num = -num;
+    }
+    
+    while (num) {
+        buf[size] = '0' + (num % 10);
+        size++;
+        num /= 10;
+    }
+    for (ix=0; ix<size/2; ix++) {
+        tmpc = buf[ix];
+        buf[ix] = buf[size-ix-1];
+        buf[size-ix-1] = tmpc;
+    }
+    buf[size] = '\0';
+}
diff --git a/src/timer.c b/src/timer.c
new file mode 100644 (file)
index 0000000..8d2087a
--- /dev/null
@@ -0,0 +1,67 @@
+#include "timer.h"
+
+extern ChimaraGlkPrivate *glk_data;
+
+/**
+ * You can request that an event be sent at fixed intervals, regardless of what
+ * the player does. Unlike input events, timer events can be tested for with
+ * glk_select_poll() as well as glk_select(). 
+ *
+ * Initially, there is no timer and you get no timer events. If you call
+ * glk_request_timer_events(N), with N not 0, you will get timer events about
+ * every N milliseconds thereafter. (Assuming that they are supported -- if
+ * not, glk_request_timer_events() has no effect.) Unlike keyboard and mouse
+ * events, timer events will continue until you shut them off. You do not have
+ * to re-request them every time you get one. Call glk_request_timer_events(0)
+ * to stop getting timer events. 
+ *
+ * The rule is that when you call glk_select() or glk_select_poll(), if it has
+ * been more than N milliseconds since the last timer event, and (for
+ * glk_select()) if there is no player input, you will receive an event whose
+ * type is evtype_Timer. (win, val1, and val2 will all be 0.) 
+ *
+ * Timer events do not stack up. If you spend 10N milliseconds doing
+ * computation, and then call glk_select(), you will not get ten timer events
+ * in a row. The library will simply note that it has been more than N
+ * milliseconds, and return a timer event right away. If you call glk_select()
+ * again immediately, it will be N milliseconds before the next timer event. 
+ *
+ * This means that the timing of timer events is approximate, and the library
+ * will err on the side of being late. If there is a conflict between player
+ * input events and timer events, the player input takes precedence. [This
+ * prevents the user from being locked out by overly enthusiastic timer events.
+ * Unfortunately, it also means that your timer can be locked out on slower
+ * machines, if the player pounds too enthusiastically on the keyboard. Sorry.
+ * If you want a real-time operating system, talk to Wind River.] 
+ *
+ * [I don't have to tell you that a millisecond is one thousandth of a second,
+ * do I?]
+ *
+ * NOTE: setting a new timer will overwrite the old timer if one was in place.
+ */
+void
+glk_request_timer_events(glui32 millisecs)
+{
+       // Stop any existing timer
+       if(glk_data->timer_id != 0) {
+               g_source_remove(glk_data->timer_id);
+               glk_data->timer_id = 0;
+       }
+
+       if(millisecs == 0)
+               return;
+       
+       glk_data->timer_id = g_timeout_add(millisecs, push_timer_event, NULL);
+}
+
+/**
+ * Internal function: push a new timer event on the event stack.
+ * Will always return TRUE
+ */
+gboolean
+push_timer_event(gpointer data)
+{
+       event_throw(evtype_Timer, NULL, 0, 0);
+
+       return TRUE;
+}
diff --git a/src/timer.h b/src/timer.h
new file mode 100644 (file)
index 0000000..1edb670
--- /dev/null
@@ -0,0 +1,10 @@
+#ifndef TIMER_H
+#define TIMER_H
+
+#include <glib.h>
+#include "event.h"
+#include "chimara-glk-private.h"
+
+G_GNUC_INTERNAL gboolean push_timer_event(gpointer  data);
+
+#endif