4: Events

As described above, all player input is handed to your program by the glk_select() call, in the form of events. You should write at least one event loop to retrieve these events.

void glk_select(event_t *event);

typedef struct event_struct {
    glui32 type;
    winid_t win;
    glui32 val1, val2;
} event_t;

This causes the program to wait for an event, and then store it in the structure pointed to by the argument. Unlike most Glk functions that take pointers, the argument of glk_select() may not be NULL.

Most of the time, you only get the events that you request. However, there are some events which can arrive at any time. This is why you must always call glk_select() in a loop, and continue the loop until you get the event you really want.

The event structure is self-explanatory. type is the event type. The window that spawned the event, if relevant, is in win. The remaining fields contain more information specific to the event.

The event types are:

Note that evtype_None is zero, and the other values are positive. Negative event types (0x80000000 to 0xFFFFFFFF) are reserved for implementation-defined events.

You can also inquire if an event is available, without stopping to wait for one to occur.

void glk_select_poll(event_t *event);

This checks if an internally-spawned event is available. If so, it stores it in the structure pointed to by event. If not, it sets event->type to evtype_None. Either way, it returns almost immediately.

The first question you now ask is, what is an internally-spawned event? glk_select_poll() does not check for or return evtype_CharInput, evtype_LineInput, or evtype_MouseInput events. It is intended for you to test conditions which may have occurred while you are computing, and not interfacing with the player. For example, time may pass during slow computations; you can use glk_select_poll() to see if a evtype_Timer event has occured. (See section 4.4, "Timer Events".)

At the moment, glk_select_poll() checks for evtype_Timer, and possibly evtype_Arrange and evtype_SoundNotify events. But see section 4.9, "Other Events".

The second question is, what does it mean that glk_select_poll() returns "almost immediately"? In some Glk libraries, text that you send to a window is buffered; it does not actually appear until you request player input with glk_select(). glk_select_poll() attends to this buffer-flushing task in the same way. (Although it does not do the "Hit any key to scroll down" waiting which may be done in glk_select(); that's a player-input task.)

Similarly, on multitasking platforms, glk_select() may yield time to other processes; and glk_select_poll() does this as well.

The upshot of this is that you should not call glk_select_poll() very often. If you are not doing much work between player inputs, you should not need to call it at all. [For example, in a virtual machine interpreter, you should not call glk_select_poll() after every opcode.] However, if you are doing intense computation, you may wish to call glk_select_poll() every so often to yield time to other processes. And if you are printing intermediate results during this computation, you should glk_select_poll() every so often, so that you can be certain your output will be displayed before the next glk_select().

[However, you should call glk_tick() often -- once per opcode in a VM interpreter. See section 1.4, "The Tick Thing".]

4.1: Character Input Events

You can request character input from text buffer, text grid, and graphics windows. There are separate functions for requesting the availability of particular Latin-1 and Unicode characters; see section 2.1, "Testing for Unicode Capabilities". To test whether graphics windows support character input, use the gestalt_GraphicsCharInput selector.

void glk_request_char_event(winid_t win);

Request input of a Latin-1 character or special key. A window cannot have requests for both character and line input at the same time. Nor can it have requests for character input of both types (Latin-1 and Unicode). It is illegal to call glk_request_char_event() if the window already has a pending request for either character or line input.

void glk_request_char_event_uni(winid_t win);

Request input of a Unicode character or special key.

void glk_cancel_char_event(winid_t win);

This cancels a pending request for character input. (Either Latin-1 or Unicode.) For convenience, it is legal to call glk_cancel_char_event() even if there is no character input request on that window. Glk will ignore the call in this case.

If a window has a pending request for character input, and the player hits a key in that window, glk_select() will return an event whose type is evtype_CharInput. Once this happens, the request is complete; it is no longer pending. You must call glk_request_char_event() or glk_request_char_event_uni() if you want another character from that window.

In the event structure, win tells what window the event came from. val1 tells what character was entered; this will be a character code, or a special keycode. (See section 2.4, "Character Input".) If you called glk_request_char_event(), val1 will be in 0..255, or else a special keycode. In any case, val2 will be 0.

4.2: Line Input Events

You can request line input from text buffer and text grid windows. There are separate functions for requesting the availability of particular Latin-1 and Unicode characters; see section 2.1, "Testing for Unicode Capabilities".

void glk_request_line_event(winid_t win, char *buf, glui32 maxlen, glui32 initlen);

Request input of a line of Latin-1 characters. A window cannot have requests for both character and line input at the same time. Nor can it have requests for line input of both types (Latin-1 and Unicode). It is illegal to call glk_request_line_event() if the window already has a pending request for either character or line input.

The buf argument is a pointer to space where the line input will be stored. (This may not be NULL.) maxlen is the length of this space, in bytes; the library will not accept more characters than this. If initlen is nonzero, then the first initlen bytes of buf will be entered as pre-existing input -- just as if the player had typed them himself. [The player can continue composing after this pre-entered input, or delete it or edit as usual.]

The contents of the buffer are undefined until the input is completed (either by a line input event, or glk_cancel_line_event(). The library may or may not fill in the buffer as the player composes, while the input is still pending; it is illegal to change the contents of the buffer yourself.

void glk_request_line_event_uni(winid_t win, glui32 *buf, glui32 maxlen, glui32 initlen);

Request input of a line of Unicode characters. This works the same as glk_request_line_event(), except the result is stored in an array of glui32 values instead of an array of characters, and the values may be any valid Unicode code points.

If possible, the library should return fully composed Unicode characters, rather than strings of base and composition characters.

[Fully-composed characters are the norm for Unicode text, so an implementation that ignores this issue will probably produce the right result. However, the game may not want to rely on that. Another factor is that case-folding can (occasionally) produce non-normalized text. Therefore, to cover all its bases, a game should call glk_buffer_to_lower_case_uni(), followed by glk_buffer_canon_normalize_uni(), before parsing.]

[Earlier versions of this spec said that line input must always be in Unicode Normalization Form C. However, this has not been universally implemented. It is also somewhat redundant, for the results noted above. Therefore, we now merely recommend that line input be fully composed. The game is ultimately responsible for all case-folding and normalization. See section 2.6, "Unicode String Normalization".]

void glk_cancel_line_event(winid_t win, event_t *event);

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_set_echo_line_event(winid_t win, glui32 val);

Normally, after line input is completed or cancelled in a buffer window, the library ensures that the complete input line (or its latest state, after cancelling) is displayed at the end of the buffer, followed by a newline. This call allows you to suppress this behavior. If the val argument is zero, all subsequent line input requests in the given window will leave the buffer unchanged after the input is completed or cancelled; the player's input will not be printed. If val is nonzero, subsequent input requests will have the normal printing behavior.

[Note that this feature is unrelated to the window's echo stream.]

res = glk_gestalt(gestalt_LineInputEcho, 0);

Not all libraries support this feature. This returns 1 if glk_set_echo_line_event() is supported, and 0 if it is not. [Remember that if it is not supported, the behavior is always the default, which is line echoing enabled.]

If you turn off line input echoing, you can reproduce the standard input behavior by following each line input event (or line input cancellation) by printing the input line, in the Input style, followed by a newline in the original style.

The glk_set_echo_line_event() does not affect a pending line input request. It also has no effect in non-buffer windows. [In a grid window, the game can overwrite the input area at will, so there is no need for this distinction.]

void glk_set_terminators_line_event(winid_t win, glui32 *keycodes, glui32 count);

If a window has a pending request for line input, the player can generally hit the enter key (in that window) to complete line input. The details will depend on the platform's native user interface.

It is possible to request that other keystrokes complete line input as well. (This allows a game to intercept function keys or other special keys during line input.) To do this, call glk_set_terminators_line_event(), and pass an array of count keycodes. These must all be special keycodes (see section 2.4, "Character Input"). Do not include regular printable characters in the array, nor keycode_Return (which represents the default enter key and will always be recognized). To return to the default behavior, pass a NULL or empty array.

The glk_set_terminators_line_event() affects subsequent line input requests in the given window. It does not affect a pending line input request. [This distinction makes life easier for interpreters that set up UI callbacks only at the start of input.]

A library may not support this feature; if it does, it may not support all special keys as terminators. (Some keystrokes are reserved for OS or interpreter control.)

res = glk_gestalt(gestalt_LineTerminators, 0);

This returns 1 if glk_set_terminators_line_event() is supported, and 0 if it is not.

res = glk_gestalt(gestalt_LineTerminatorKey, ch);

This returns 1 if the keycode ch can be passed to glk_set_terminators_line_event(). If it returns 0, that keycode will be ignored as a line terminator. Printable characters and keycode_Return will always return 0.

When line input is completed, glk_select() will return an event whose type is evtype_LineInput. Once this happens, the request is complete; it is no longer pending. You must call glk_request_line_event() if you want another line of text from that window.

In the event structure, win tells what window the event came from. val1 tells how many characters were entered. val2 will be 0 unless input was ended by a special terminator key, in which case val2 will be the keycode (one of the values passed to glk_set_terminators_line_event()).

The characters themselves are stored in the buffer specified in the original glk_request_line_event() or glk_request_line_event_uni() call. [There is no null terminator or newline stored in the buffer.]

It is illegal to print anything to a window which has line input pending. [This is because the window may be displaying and editing the player's input, and printing anything would make life unnecessarily complicated for the library.]

4.3: Mouse Input Events

On some platforms, Glk can recognize when the mouse (or other pointer) is used to select a spot in a window. You can request mouse input only in text grid windows and graphics windows.

void glk_request_mouse_event(winid_t win);
void glk_cancel_mouse_event(winid_t win);

A window can have mouse input and character/line input pending at the same time.

If the player clicks in a window which has a mouse input event pending, glk_select() will return an event whose type is evtype_MouseInput. Again, once this happens, the request is complete, and you must request another if you want further mouse input.

In the event structure, win tells what window the event came from.

In a text grid window, the val1 and val2 fields are the x and y coordinates of the character that was clicked on. [So val1 is the column, and val2 is the row.] The top leftmost character is considered to be (0,0).

In a graphics window, they are the x and y coordinates of the pixel that was clicked on. Again, the top left corner of the window is (0,0).

You can test whether mouse input is supported with the gestalt_MouseInput selector.

res = glk_gestalt(gestalt_MouseInput, windowtype);

This will return TRUE (1) if windows of the given type support mouse input. If this returns FALSE (0), it is still legal to call glk_request_mouse_event(), but it will have no effect, and you will never get mouse events.

[Most mouse-based idioms define standard functions for mouse hits in text windows -- typically selecting or copying text. It is up to the library to separate this from Glk mouse input. The library may choose to select text when it is clicked normally, and cause Glk mouse events when text is control-clicked. Or the other way around. Or it may be the difference between clicking and double-clicking. Or the library may reserve a particular mouse button, on a multi-button mouse. It may even specify a keyboard key to be the "mouse button", referring to wherever the mouse cursor is when the key is hit. Or some even more esoteric positioning system. You need only know that the user can do it, or not.]

[However, since different platforms will handle this issue differently, you should be careful how you instruct the player in your program. Do not tell the player to "double-click", "right-click", or "control-click" in a window. The preferred term is "to touch the window", or a spot in the window.] [Goofy, but preferred.]

4.4: Timer Events

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().

void glk_request_timer_events(glui32 millisecs);

It is possible that the library does not support timer events. You can check this with the gestalt_Timer selector.

res = glk_gestalt(gestalt_Timer, 0);

This returns TRUE (1) if timer events are supported, and FALSE (0) if they are not.

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.]

[I don't have to tell you that a millisecond is one thousandth of a second, do I?]

4.5: Window Arrangement Events

Some platforms allow the player to resize the Glk window during play. This will naturally change the sizes of your windows. If this occurs, then immediately after all the rearrangement, glk_select() will return an event whose type is evtype_Arrange. You can use this notification to redisplay the contents of a graphics or text grid window whose size has changed. [The display of a text buffer window is entirely up to the library, so you don't need to worry about those.]

In the event structure, win will be NULL if all windows are affected. If only some windows are affected, win will refer to a window which contains all the affected windows. [You can always play it safe, ignore win, and redraw every graphics and text grid window.] val1 and val2 will be 0.

An arrangement event is guaranteed to occur whenever the player causes any window to change size, as measured by its own metric. [Size changes caused by you -- for example, if you open, close, or resize a window -- do not trigger arrangement events. You must be aware of the effects of your window management, and redraw the windows that you affect.]

[It is possible that several different player actions can cause windows to change size. For example, if the player changes the screen resolution, an arrangement event might be triggered. This might also happen if the player changes his display font to a different size; the windows would then be different "sizes" in the metric of rows and columns, which is the important metric and the only one you have access to.]

Arrangement events, like timer events, can be returned by glk_select_poll(). But this will not occur on all platforms. You must be ready to receive an arrangement event when you call glk_select_poll(), but it is possible that it will not arrive until the next time you call glk_select(). [This is because on some platforms, window resizing is handled as part of player input; on others, it can be triggered by an external process such as a window manager.]

4.6: Window Redrawing Events

On platforms that support graphics, it is possible that the contents of a graphics window will be lost, and have to be redrawn from scratch. If this occurs, then glk_select() will return an event whose type is evtype_Redraw.

In the event structure, win will be NULL if all windows are affected. If only some windows are affected, win will refer to a window which contains all the affected windows. [You can always play it safe, ignore win, and redraw every graphics window.] val1 and val2 will be 0.

Affected windows are already cleared to their background color when you receive the redraw event.

Redraw events can be returned by glk_select_poll(). But, like arrangement events, this is platform-dependent. See section 4.5, "Window Arrangement Events".

For more about redraw events and how they affect graphics windows, see section 3.5.5, "Graphics Windows".

4.7: Sound Notification Events

On platforms that support sound, you can request to receive an evtype_SoundNotify event when a sound finishes playing. You can also request to receive an evtype_VolumeNotify event when a gradual volume change completes. See section 8.3, "Playing Sounds".

4.8: Hyperlink Events

On platforms that support hyperlinks, you can request to receive an evtype_Hyperlink event when the player selects a link. See section 9.2, "Accepting Hyperlink Events".

4.9: Other Events

There are currently no other event types defined by Glk. (The "evtype_None" constant is a placeholder, and is never returned by glk_select().)

It is possible that new event types will be defined in the future. [For example, if video or animation capabilities are added to Glk, there would probably be some sort of completion event for them.]

[This is also why you must put calls to glk_select() in loops. If you tried to read a character by simply writing

glk_request_char_event(win);
glk_select(&ev);

you might not get a CharInput event back. You could get some not-yet-defined event which happened to occur before the player hit a key. Or, for that matter, a window arrangement event.]

[It is not generally necessary to put a call to glk_select_poll() in a loop. You usually call glk_select_poll() to update the display or test if an event is available, not to wait for a particular event. However, if you are using sound notification events, and several sounds are playing, it might be important to make sure you knew about all sounds completed at any particular time. You would do this with

glk_select_poll(&ev);
while (ev.type != evtype_None) {
    /* handle event */
    glk_select_poll(&ev);
}

Once glk_select_poll() returns evtype_None, you should not call it again immediately. Do some work first. If you want to wait for an event, use glk_select(), not a loop of glk_select_poll().]


Up to top Previous chapter Next chapter