Capturing keyboard commands in z-Machine / Glulx

So I’m trying to add a typical help-menu function to Swedish Inform. Navigation is the standard key-press deal – arrow keys and return to move, X to quit. That means I need keyboard capture.

Problem is, while Swedish Inform will compile to both z-machine and Glulx, getting keyboard input from (what I presume to be) the main window is much harder in Glulx than it is on the z-Machine.

It’s confusing. You can compile a game under Glulx and it will run. You can use graphics, as per the examples they give. But when I try the snippets I find to make a “press this key to exit loop” style function, the cursor keeps blinking after the prompting text appears, and the routine itself just freezes then and there. I get the feeling I’m missing something fundamental, probably having to do with streams.

So which streams are open by default, when a Glulx program is running? What am I missing?

This is what I’ve got, taken from the specs.

!% +language_name=Swedish
Include "SweAlpha";

Constant Story      "";

Include "Parser";
Include "VerbLib";
Include "SweRout";
Include "SwedishG";
Include "Infglk";

Object Rum1 "Rummet"
  with description "",
  has light;

#Ifdef TARGET_GLULX;
[ read_char
    ch done;
#Ifnot;
[ read_char
    ch;
#Endif;
! read_char is included as it is the best way to force the buffer to be
! emptied

    #ifdef TARGET_ZCODE;
        @read 1 dummy;
    #ifnot; ! TARGET_GLULX;
        done = false;
        glk($00D2, gg_mainwin); ! request_char_event
        while (~~done) {
            glk($00C0, gg_event); ! select
            switch (gg_event-->0) {
                5: ! evtype_Arrange
                    DrawStatusLine();
                2: ! evtype_CharInput
                    if (gg_event-->1 == gg_mainwin)
                        done = true;
            }
        }
    #endif; ! TARGET_
  return ch;
];

[Initialise;
  location=Rum1;
  "^^^Text.^^";
];

[ HelpSub key;
  print "See below.";
  do {
    key = read_char();

  } until ((key == 'Q') ||
  (key == 'X') || (key == 'q') ||
  (key == 'x'));
];
Verb 'help'
   *                                       -> Help;
1 Like

You might be better off making use of the Standard Library routine KeyCharPrimitive(), as this appears to handle the Glulx details automatically. I did a quick test with Inform 6.31 and StdLib 6/11, and it seemed to work.

[ HelpSub key;
  print "See below.";
  do {
    key = KeyCharPrimitive();

  } until ((key == 'Q') ||
  (key == 'X') || (key == 'q') ||
  (key == 'x'));
];

I’m not sure whether that routine is in the Swedish library, but if not, seeing what it does may help:

[ KeyCharPrimitive win nostat done res ix jx ch;
    jx = ch; ! squash compiler warnings
    if (win == 0) win = gg_mainwin;
    if (gg_commandstr ~= 0 && gg_command_reading ~= false) {
        ! get_line_stream
        done = glk_get_line_stream(gg_commandstr, gg_arguments, 31);
        if (done == 0) {
            glk_stream_close(gg_commandstr, 0);
            gg_commandstr = 0;
            gg_command_reading = false;
            ! fall through to normal user input.
        }
        else {
            ! Trim the trailing newline
            if (gg_arguments->(done-1) == 10) done = done-1;
            res = gg_arguments->0;
            if (res == '\') {
                res = 0;
                for (ix=1 : ix<done : ix++) {
                    ch = gg_arguments->ix;
                    if (ch >= '0' && ch <= '9') {
                        @shiftl res 4 res;
                        res = res + (ch-'0');
                    }
                    else if (ch >= 'a' && ch <= 'f') {
                        @shiftl res 4 res;
                        res = res + (ch+10-'a');
                    }
                    else if (ch >= 'A' && ch <= 'F') {
                        @shiftl res 4 res;
                        res = res + (ch+10-'A');
                    }
                }
            }
        jump KCPContinue;
        }
    }
    done = false;
    glk_request_char_event(win);
    while (~~done) {
        glk_select(gg_event);
        switch (gg_event-->0) {
          5: ! evtype_Arrange
            if (nostat) {
                glk_cancel_char_event(win);
                res = $80000000;
                done = true;
                break;
            }
            DrawStatusLine();
          2: ! evtype_CharInput
            if (gg_event-->1 == win) {
                res = gg_event-->2;
                done = true;
                }
        }
        ix = HandleGlkEvent(gg_event, 1, gg_arguments);
        if (ix == 0) ix = LibraryExtensions.RunWhile(ext_handleglkevent, 0, gg_event, 1, gg_arguments);
        if (ix == 2) {
            res = gg_arguments-->0;
            done = true;
        }
        else if (ix == -1) {
            done = false;
        }
    }
    if (gg_commandstr ~= 0 && gg_command_reading == false) {
        if (res < 32 || res >= 256 || (res == '\' or ' ')) {
            glk_put_char_stream(gg_commandstr, '\');
            done = 0;
            jx = res;
            for (ix=0 : ix<8 : ix++) {
                @ushiftr jx 28 ch;
                @shiftl jx 4 jx;
                ch = ch & $0F;
                if (ch ~= 0 || ix == 7) done = 1;
                if (done) {
                    if (ch >= 0 && ch <= 9) ch = ch + '0';
                    else                    ch = (ch - 10) + 'A';
                    glk_put_char_stream(gg_commandstr, ch);
                }
            }
        }
        else {
            glk_put_char_stream(gg_commandstr, res);
        }
        glk_put_char_stream(gg_commandstr, 10);
    }
  .KCPContinue;
    return res;
];
2 Likes

Thank you, that works a treat!

I just got it to work a moment ago by


#Ifdef TARGET_GLULX;
[ read_char
    ch done;
#Ifnot;
[ read_char
    ch;
#Endif;
! read_char is included as it is the best way to force the buffer to be
! emptied

    #ifdef TARGET_ZCODE;
        @read 1 dummy;
    #ifnot; ! TARGET_GLULX;
        done = false;
        glk($00D2, gg_mainwin); ! request_char_event
        while (~~done) {
            glk($00C0, gg_event); ! select
            switch (gg_event-->0) {
                5: ! evtype_Arrange
                    DrawStatusLine();
                2: ! evtype_CharInput
                    if (gg_event-->1 == gg_mainwin) {
                        ch = gg_event-->2;
                        done = true;
                    }
            }
        }
    #endif; ! TARGET_
  return ch;
];

…but… yeah. Using KeyCharPrimitive() is obviously far superior.