+/*-
+ * Copyright 2010-2012 Chris Spiegel.
+ *
+ * This file is part of Bocfel.
+ *
+ * Bocfel is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License, version
+ * 2 or 3, as published by the Free Software Foundation.
+ *
+ * Bocfel is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Bocfel. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include <stddef.h>
+#include <stdarg.h>
+
+#ifdef ZTERP_GLK
+#include <glk.h>
+#include <libchimara/garglk.h>
+#endif
+
+#include "screen.h"
+#include "branch.h"
+#include "dict.h"
+#include "io.h"
+#include "memory.h"
+#include "objects.h"
+#include "osdep.h"
+#include "process.h"
+#include "stack.h"
+#include "unicode.h"
+#include "util.h"
+#include "zterp.h"
+
+static struct window
+{
+ unsigned style;
+
+ enum font { FONT_NONE = -1, FONT_PREVIOUS, FONT_NORMAL, FONT_PICTURE, FONT_CHARACTER, FONT_FIXED } font;
+ enum font prev_font;
+
+#ifdef ZTERP_GLK
+ winid_t id;
+ long x, y; /* Only meaningful for window 1 */
+ int pending_read;
+ union line
+ {
+ char latin1[256];
+ glui32 unicode[256];
+ } *line;
+ int has_echo;
+#endif
+} windows[8], *mainwin = &windows[0], *curwin = &windows[0];
+#ifdef ZTERP_GLK
+static struct window *upperwin = &windows[1];
+static struct window statuswin;
+static long upper_window_height = 0;
+static long upper_window_width = 0;
+static winid_t errorwin;
+#endif
+
+/* In all versions but 6, styles are global and stored in mainwin. For
+ * V6, styles are tracked per window and thus stored in each individual
+ * window. For convenience, this macro expands to the “style window”
+ * for any version.
+ */
+#define style_window (zversion == 6 ? curwin : mainwin)
+
+/* If the window needs to be temporarily switched (@show_status and
+ * @print_form print to specific windows, and window_change() might
+ * temporarily need to switch to the upper window), the code that
+ * requires a specific window can be wrapped in these macros.
+ */
+#ifdef ZTERP_GLK
+#define SWITCH_WINDOW_START(win) { struct window *saved_ = curwin; curwin = (win); glk_set_window((win)->id);
+#define SWITCH_WINDOW_END() curwin = saved_; glk_set_window(curwin->id); }
+#else
+#define SWITCH_WINDOW_START(win) { struct window *saved_ = curwin; curwin = (win);
+#define SWITCH_WINDOW_END() curwin = saved_; }
+#endif
+
+/* Output stream bits. */
+#define STREAM_SCREEN (1U << 1)
+#define STREAM_TRANS (1U << 2)
+#define STREAM_MEMORY (1U << 3)
+#define STREAM_SCRIPT (1U << 4)
+
+static unsigned int streams = STREAM_SCREEN;
+static zterp_io *transio, *scriptio;
+
+static struct
+{
+ uint16_t table;
+ uint16_t i;
+} stables[16];
+static int stablei = -1;
+
+static int istream = ISTREAM_KEYBOARD;
+static zterp_io *istreamio;
+
+struct input
+{
+ enum { INPUT_CHAR, INPUT_LINE } type;
+
+ /* ZSCII value of key read for @read_char. */
+ uint8_t key;
+
+ /* Unicode line of chars read for @read. */
+ uint32_t *line;
+ uint8_t maxlen;
+ uint8_t len;
+ uint8_t preloaded;
+
+ /* Character used to terminate input. If terminating keys are not
+ * supported by the Glk implementation being used (or if Glk is not
+ * used at all) this will be ZSCII_NEWLINE; or in the case of
+ * cancellation, 0.
+ */
+ uint8_t term;
+};
+
+/* This macro makes it so that code elsewhere needn’t check have_unicode before printing. */
+#define GLK_PUT_CHAR(c) do { if(!have_unicode) glk_put_char(unicode_to_latin1[c]); else glk_put_char_uni(c); } while(0)
+
+void show_message(const char *fmt, ...)
+{
+ va_list ap;
+ char message[1024];
+
+ va_start(ap, fmt);
+ vsnprintf(message, sizeof message, fmt, ap);
+ va_end(ap);
+
+#ifdef ZTERP_GLK
+ static int error_lines = 0;
+
+ if(errorwin != NULL)
+ {
+ glui32 w, h;
+
+ /* Allow multiple messages to stack, but force at least 5 lines to
+ * always be visible in the main window. This is less than perfect
+ * because it assumes that each message will be less than the width
+ * of the screen, but it’s not a huge deal, really; even if the
+ * lines are too long, at least Gargoyle and glktermw are graceful
+ * enough.
+ */
+ glk_window_get_size(mainwin->id, &w, &h);
+
+ if(h > 5) glk_window_set_arrangement(glk_window_get_parent(errorwin), winmethod_Below | winmethod_Fixed, ++error_lines, errorwin);
+ glk_put_char_stream(glk_window_get_stream(errorwin), UNICODE_LINEFEED);
+ }
+ else
+ {
+ errorwin = glk_window_open(mainwin->id, winmethod_Below | winmethod_Fixed, error_lines = 2, wintype_TextBuffer, 0);
+ }
+
+ /* If windows are not supported (e.g. in cheapglk), messages will not
+ * get displayed. If this is the case, print to the main window.
+ */
+ if(errorwin != NULL)
+ {
+ glk_set_style_stream(glk_window_get_stream(errorwin), style_Alert);
+ glk_put_string_stream(glk_window_get_stream(errorwin), message);
+ }
+ else
+ {
+ SWITCH_WINDOW_START(mainwin);
+ glk_put_string("\12[");
+ glk_put_string(message);
+ glk_put_string("]\12");
+ SWITCH_WINDOW_END();
+ }
+#else
+ /* In Glk messages go to a separate window, but they're interleaved in
+ * non-Glk. Put brackets around the message in an attempt to offset
+ * it from the game a bit.
+ */
+ fprintf(stderr, "\n[%s]\n", message);
+#endif
+}
+
+/* See §7.
+ * This returns true if the stream was successfully selected.
+ * Deselecting a stream is always successful.
+ */
+int output_stream(int16_t number, uint16_t table)
+{
+ if(number > 0)
+ {
+ streams |= 1U << number;
+ }
+ else if(number < 0)
+ {
+ if(number != -3 || stablei == 0) streams &= ~(1U << -number);
+ }
+
+ if(number == 2)
+ {
+ STORE_WORD(0x10, WORD(0x10) | FLAGS2_TRANSCRIPT);
+ if(transio == NULL)
+ {
+ transio = zterp_io_open(options.transcript_name, ZTERP_IO_TRANS | (options.overwrite_transcript ? ZTERP_IO_WRONLY : ZTERP_IO_APPEND));
+ if(transio == NULL)
+ {
+ STORE_WORD(0x10, WORD(0x10) & ~FLAGS2_TRANSCRIPT);
+ streams &= ~STREAM_TRANS;
+ warning("unable to open the transcript");
+ }
+ }
+ }
+ else if(number == -2)
+ {
+ STORE_WORD(0x10, WORD(0x10) & ~FLAGS2_TRANSCRIPT);
+ }
+
+ if(number == 3)
+ {
+ stablei++;
+ ZASSERT(stablei < 16, "too many stream tables");
+
+ stables[stablei].table = table;
+ user_store_word(stables[stablei].table, 0);
+ stables[stablei].i = 2;
+ }
+ else if(number == -3 && stablei >= 0)
+ {
+ user_store_word(stables[stablei].table, stables[stablei].i - 2);
+ stablei--;
+ }
+
+ if(number == 4)
+ {
+ if(scriptio == NULL)
+ {
+ scriptio = zterp_io_open(options.record_name, ZTERP_IO_WRONLY | ZTERP_IO_INPUT);
+ if(scriptio == NULL)
+ {
+ streams &= ~STREAM_SCRIPT;
+ warning("unable to open the script");
+ }
+ }
+ }
+ /* XXX v6 has even more handling */
+
+ return number < 0 || (streams & (1U << number));
+}
+
+void zoutput_stream(void)
+{
+ output_stream(zargs[0], zargs[1]);
+}
+
+/* See §10.
+ * This returns true if the stream was successfully selected.
+ */
+int input_stream(int which)
+{
+ istream = which;
+
+ if(istream == ISTREAM_KEYBOARD)
+ {
+ if(istreamio != NULL)
+ {
+ zterp_io_close(istreamio);
+ istreamio = NULL;
+ }
+ }
+ else if(istream == ISTREAM_FILE)
+ {
+ if(istreamio == NULL)
+ {
+ istreamio = zterp_io_open(options.replay_name, ZTERP_IO_INPUT | ZTERP_IO_RDONLY);
+ if(istreamio == NULL)
+ {
+ warning("unable to open the command script");
+ istream = ISTREAM_KEYBOARD;
+ }
+ }
+ }
+ else
+ {
+ ZASSERT(0, "invalid input stream: %d", istream);
+ }
+
+ return istream == which;
+}
+
+void zinput_stream(void)
+{
+ input_stream(zargs[0]);
+}
+
+/* This does not even pretend to understand V6 windows. */
+static void set_current_window(struct window *window)
+{
+ curwin = window;
+
+#ifdef ZTERP_GLK
+ if(curwin == upperwin && upperwin->id != NULL)
+ {
+ upperwin->x = upperwin->y = 0;
+ glk_window_move_cursor(upperwin->id, 0, 0);
+ }
+
+ glk_set_window(curwin->id);
+#endif
+
+ set_current_style();
+}
+
+/* Find and validate a window. If window is -3 and the story is V6,
+ * return the current window.
+ */
+static struct window *find_window(uint16_t window)
+{
+ int16_t w = window;
+
+ ZASSERT(zversion == 6 ? w == -3 || (w >= 0 && w < 8) : w == 0 || w == 1, "invalid window selected: %d", w);
+
+ if(w == -3) return curwin;
+
+ return &windows[w];
+}
+
+#ifdef ZTERP_GLK
+/* When resizing the upper window, the screen’s contents should not
+ * change (§8.6.1); however, the way windows are handled with Glk makes
+ * this slightly impossible. When an Inform game tries to display
+ * something with “box”, it expands the upper window, displays the quote
+ * box, and immediately shrinks the window down again. This is a
+ * problem under Glk because the window immediately disappears. Other
+ * games, such as Bureaucracy, expect the upper window to shrink as soon
+ * as it has been requested. Thus the following system is used:
+ *
+ * If a request is made to shrink the upper window, it is granted
+ * immediately if there has been user input since the last window resize
+ * request. If there has not been user input, the request is delayed
+ * until after the next user input is read.
+ */
+static long delayed_window_shrink = -1;
+static int saw_input;
+
+static void update_delayed(void)
+{
+ glui32 height;
+
+ if(delayed_window_shrink == -1 || upperwin->id == NULL) return;
+
+ glk_window_set_arrangement(glk_window_get_parent(upperwin->id), winmethod_Above | winmethod_Fixed, delayed_window_shrink, upperwin->id);
+ upper_window_height = delayed_window_shrink;
+
+ /* Glk might resize the window to a smaller height than was requested,
+ * so track the actual height, not the requested height.
+ */
+ glk_window_get_size(upperwin->id, NULL, &height);
+ if(height != upper_window_height)
+ {
+ /* This message probably won’t be seen in a window since the upper
+ * window is likely covering everything, but try anyway.
+ */
+ show_message("Unable to fulfill window size request: wanted %ld, got %lu", delayed_window_shrink, (unsigned long)height);
+ upper_window_height = height;
+ }
+
+ delayed_window_shrink = -1;
+}
+
+/* Both the upper and lower windows have their own issues to deal with
+ * when there is line input. This function ensures that the cursor
+ * position is properly tracked in the upper window, and if possible,
+ * aids in the suppression of newline printing on input cancellation in
+ * the lower window.
+ */
+static void cleanup_screen(struct input *input)
+{
+ if(input->type != INPUT_LINE) return;
+
+ /* If the current window is the upper window, the position of the
+ * cursor needs to be tracked, so after a line has successfully been
+ * read, advance the cursor to the initial position of the next line,
+ * or if a terminating key was used or input was canceled, to the end
+ * of the input.
+ */
+ if(curwin == upperwin)
+ {
+ if(input->term != ZSCII_NEWLINE) upperwin->x += input->len;
+
+ if(input->term == ZSCII_NEWLINE || upperwin->x >= upper_window_width)
+ {
+ upperwin->x = 0;
+ if(upperwin->y < upper_window_height) upperwin->y++;
+ }
+
+ glk_window_move_cursor(upperwin->id, upperwin->x, upperwin->y);
+ }
+
+ /* If line input echoing is turned off, newlines will not be printed
+ * when input is canceled, but neither will the input line. Fix that.
+ */
+ if(curwin->has_echo)
+ {
+ glk_set_style(style_Input);
+ for(int i = 0; i < input->len; i++) GLK_PUT_CHAR(input->line[i]);
+ if(input->term == ZSCII_NEWLINE) glk_put_char(UNICODE_LINEFEED);
+ set_current_style();
+ }
+}
+
+/* In an interrupt, if the story tries to read or write, the previous
+ * read event (which triggered the interrupt) needs to be canceled.
+ * This function does the cancellation.
+ */
+static void cancel_read_events(struct window *window)
+{
+ if(window->pending_read)
+ {
+ event_t ev;
+
+ glk_cancel_char_event(window->id);
+ glk_cancel_line_event(window->id, &ev);
+
+ /* If the pending read was a line input, zero terminate the string
+ * so when it’s re-requested the length of the already-loaded
+ * portion can be discovered. Also deal with cursor positioning in
+ * the upper window, and line echoing in the lower window.
+ */
+ if(ev.type == evtype_LineInput && window->line != NULL)
+ {
+ uint32_t line[ev.val1];
+ struct input input = { .type = INPUT_LINE, .line = line, .term = 0, .len = ev.val1 };
+
+ if(have_unicode) window->line->unicode[ev.val1] = 0;
+ else window->line->latin1 [ev.val1] = 0;
+
+ for(int i = 0; i < input.len; i++)
+ {
+ if(have_unicode) line[i] = window->line->unicode[i];
+ else line[i] = window->line->latin1 [i];
+ }
+
+ cleanup_screen(&input);
+ }
+
+ window->pending_read = 0;
+ window->line = NULL;
+ }
+}
+
+static void clear_window(struct window *window)
+{
+ if(window->id == NULL) return;
+
+ /* glk_window_clear() cannot be used while there are pending read requests. */
+ cancel_read_events(window);
+
+ glk_window_clear(window->id);
+
+ window->x = window->y = 0;
+}
+#endif
+
+/* If restoring from an interrupt (which is a bad idea to begin with),
+ * it’s entirely possible that there will be pending read events that
+ * need to be canceled, so allow that.
+ */
+void cancel_all_events(void)
+{
+#ifdef ZTERP_GLK
+ for(int i = 0; i < 8; i++) cancel_read_events(&windows[i]);
+#endif
+}
+
+static void resize_upper_window(long nlines)
+{
+#ifdef ZTERP_GLK
+ if(upperwin->id == NULL) return;
+
+ /* To avoid code duplication, put all window resizing code in
+ * update_delayed() and, if necessary, call it from here.
+ */
+ delayed_window_shrink = nlines;
+ if(upper_window_height <= nlines || saw_input) update_delayed();
+
+ saw_input = 0;
+
+ /* §8.6.1.1.2 */
+ if(zversion == 3) clear_window(upperwin);
+
+ /* As in a few other areas, changing the upper window causes reverse
+ * video to be deactivated, so reapply the current style.
+ */
+ set_current_style();
+#endif
+}
+
+void close_upper_window(void)
+{
+ /* The upper window is never destroyed; rather, when it’s closed, it
+ * shrinks to zero height.
+ */
+ resize_upper_window(0);
+
+#ifdef ZTERP_GLK
+ delayed_window_shrink = -1;
+ saw_input = 0;
+#endif
+
+ set_current_window(mainwin);
+}
+
+void get_screen_size(unsigned int *width, unsigned int *height)
+{
+ *width = 80;
+ *height = 24;
+
+#ifdef ZTERP_GLK
+ glui32 w, h;
+
+ /* The main window can be proportional, and if so, its width is not
+ * generally useful because games tend to care about width with a
+ * fixed font. If a status window is available, or if an upper window
+ * is available, use that to calculate the width, because these
+ * windows will have a fixed-width font. The height is the combined
+ * height of all windows.
+ */
+ glk_window_get_size(mainwin->id, &w, &h);
+ *height = h;
+ if(statuswin.id != NULL)
+ {
+ glk_window_get_size(statuswin.id, &w, &h);
+ *height += h;
+ }
+ if(upperwin->id != NULL)
+ {
+ glk_window_get_size(upperwin->id, &w, &h);
+ *height += h;
+ }
+ *width = w;
+#else
+ zterp_os_get_screen_size(width, height);
+#endif
+
+ /* XGlk does not report the size of textbuffer windows, so here’s a safety net. */
+ if(*width == 0) *width = 80;
+ if(*height == 0) *height = 24;
+
+ /* Terrible hack: Because V6 is not properly supported, the window to
+ * which Journey writes its story is completely covered up by window
+ * 1. For the same reason, only the bottom 6 lines of window 1 are
+ * actually useful, even though the game expands it to cover the whole
+ * screen. By pretending that the screen height is only 6, the main
+ * window, where text is actually sent, becomes visible.
+ */
+ if(is_story("83-890706") && *height > 6) *height = 6;
+}
+
+#ifdef GLK_MODULE_LINE_TERMINATORS
+static uint32_t *term_keys, term_size, term_nkeys;
+
+void term_keys_reset(void)
+{
+ free(term_keys);
+ term_keys = NULL;
+ term_size = 0;
+ term_nkeys = 0;
+}
+
+static void insert_key(uint32_t key)
+{
+ if(term_nkeys == term_size)
+ {
+ term_size += 32;
+
+ term_keys = realloc(term_keys, term_size * sizeof *term_keys);
+ if(term_keys == NULL) die("unable to allocate memory for terminating keys");
+ }
+
+ term_keys[term_nkeys++] = key;
+}
+
+void term_keys_add(uint8_t key)
+{
+ switch(key)
+ {
+ case 129: insert_key(keycode_Up); break;
+ case 130: insert_key(keycode_Down); break;
+ case 131: insert_key(keycode_Left); break;
+ case 132: insert_key(keycode_Right); break;
+ case 133: insert_key(keycode_Func1); break;
+ case 134: insert_key(keycode_Func2); break;
+ case 135: insert_key(keycode_Func3); break;
+ case 136: insert_key(keycode_Func4); break;
+ case 137: insert_key(keycode_Func5); break;
+ case 138: insert_key(keycode_Func6); break;
+ case 139: insert_key(keycode_Func7); break;
+ case 140: insert_key(keycode_Func8); break;
+ case 141: insert_key(keycode_Func9); break;
+ case 142: insert_key(keycode_Func10); break;
+ case 143: insert_key(keycode_Func11); break;
+ case 144: insert_key(keycode_Func12); break;
+
+ /* Keypad 0–9 should be here, but Glk doesn’t support that. */
+ case 145: case 146: case 147: case 148: case 149:
+ case 150: case 151: case 152: case 153: case 154:
+ break;
+
+ /* Mouse clicks would go here if I supported them. */
+ case 252: case 253: case 254:
+ break;
+
+ case 255:
+ for(int i = 129; i <= 144; i++) term_keys_add(i);
+ break;
+
+ default:
+ ZASSERT(0, "invalid terminating key: %u", (unsigned)key);
+ break;
+ }
+}
+#endif
+
+/* Print out a character. The character is in “c” and is either Unicode
+ * or ZSCII; if the former, “unicode” is true.
+ */
+static void put_char_base(uint16_t c, int unicode)
+{
+ if(c == 0) return;
+
+ if(streams & STREAM_MEMORY)
+ {
+ ZASSERT(stablei != -1, "invalid stream table");
+
+ /* When writing to memory, ZSCII should always be used (§7.5.3). */
+ if(unicode) c = unicode_to_zscii_q[c];
+
+ user_store_byte(stables[stablei].table + stables[stablei].i++, c);
+ }
+ else
+ {
+ /* For screen and transcription, always prefer Unicode. */
+ if(!unicode) c = zscii_to_unicode[c];
+
+ if(c != 0)
+ {
+ uint8_t zscii = 0;
+
+ /* §16 makes no mention of what a newline in font 3 should map to.
+ * Other interpreters that implement font 3 assume it stays a
+ * newline, and this makes the most sense, so don’t do any
+ * translation in that case.
+ */
+ if(curwin->font == FONT_CHARACTER && !options.disable_graphics_font && c != UNICODE_LINEFEED)
+ {
+ zscii = unicode_to_zscii[c];
+
+ /* These four characters have a “built-in” reverse video (see §16). */
+ if(zscii >= 123 && zscii <= 126)
+ {
+ style_window->style ^= STYLE_REVERSE;
+ set_current_style();
+ }
+
+ c = zscii_to_font3[zscii];
+ }
+#ifdef ZTERP_GLK
+ if((streams & STREAM_SCREEN) && curwin->id != NULL)
+ {
+ cancel_read_events(curwin);
+
+ if(curwin == upperwin)
+ {
+ /* Interpreters seem to have differing ideas about what
+ * happens when the cursor reaches the end of a line in the
+ * upper window. Some wrap, some let it run off the edge (or,
+ * at least, stop the text at the edge). The standard, from
+ * what I can see, says nothing on this issue. Follow Windows
+ * Frotz and don’t wrap.
+ */
+
+ if(c == UNICODE_LINEFEED)
+ {
+ if(upperwin->y < upper_window_height)
+ {
+ /* Glk wraps, so printing a newline when the cursor has
+ * already reached the edge of the screen will produce two
+ * newlines.
+ */
+ if(upperwin->x < upper_window_width) GLK_PUT_CHAR(c);
+
+ /* Even if a newline isn’t explicitly printed here
+ * (because the cursor is at the edge), setting
+ * upperwin->x to 0 will cause the next character to be on
+ * the next line because the text will have wrapped.
+ */
+ upperwin->x = 0;
+ upperwin->y++;
+ }
+ }
+ else if(upperwin->x < upper_window_width && upperwin->y < upper_window_height)
+ {
+ upperwin->x++;
+ GLK_PUT_CHAR(c);
+ }
+ }
+ else
+ {
+ GLK_PUT_CHAR(c);
+ }
+ }
+#else
+ if((streams & STREAM_SCREEN) && curwin == mainwin) zterp_io_putc(zterp_io_stdout(), c);
+#endif
+
+ /* If the reverse video bit was flipped (for the character font), flip it back. */
+ if(zscii >= 123 && zscii <= 126)
+ {
+ style_window->style ^= STYLE_REVERSE;
+ set_current_style();
+ }
+
+ if((streams & STREAM_TRANS) && curwin == mainwin) zterp_io_putc(transio, c);
+ }
+ }
+}
+
+void put_char_u(uint16_t c)
+{
+ put_char_base(c, 1);
+}
+
+void put_char(uint8_t c)
+{
+ put_char_base(c, 0);
+}
+
+static void put_string(const char *s)
+{
+ for(; *s != 0; s++)
+ {
+ if(*s == '\n') put_char(ZSCII_NEWLINE);
+ else put_char(*s);
+ }
+}
+
+/* Decode and print a zcode string at address “addr”. This can be
+ * called recursively thanks to abbreviations; the initial call should
+ * have “in_abbr” set to 0.
+ * Each time a character is decoded, it is passed to the function
+ * “outc”.
+ */
+static int print_zcode(uint32_t addr, int in_abbr, void (*outc)(uint8_t))
+{
+ int abbrev = 0, shift = 0, special = 0;
+ int c, lastc = 0; /* Initialize lastc to shut gcc up */
+ uint16_t w;
+ uint32_t counter = addr;
+ int current_alphabet = 0;
+
+ do
+ {
+ ZASSERT(counter < memory_size - 1, "string runs beyond the end of memory");
+
+ w = WORD(counter);
+
+ for(int i = 10; i >= 0; i -= 5)
+ {
+ c = (w >> i) & 0x1f;
+
+ if(special)
+ {
+ if(special == 2) lastc = c;
+ else outc((lastc << 5) | c);
+
+ special--;
+ }
+
+ else if(abbrev)
+ {
+ uint32_t new_addr;
+
+ new_addr = user_word(header.abbr + 64 * (abbrev - 1) + 2 * c);
+
+ /* new_addr is a word address, so multiply by 2 */
+ print_zcode(new_addr * 2, 1, outc);
+
+ abbrev = 0;
+ }
+
+ else switch(c)
+ {
+ case 0:
+ outc(ZSCII_SPACE);
+ shift = 0;
+ break;
+ case 1:
+ if(zversion == 1)
+ {
+ outc(ZSCII_NEWLINE);
+ shift = 0;
+ break;
+ }
+ /* fallthrough */
+ case 2: case 3:
+ if(zversion >= 3 || (zversion == 2 && c == 1))
+ {
+ ZASSERT(!in_abbr, "abbreviation being used recursively");
+ abbrev = c;
+ shift = 0;
+ }
+ else
+ {
+ shift = c - 1;
+ }
+ break;
+ case 4: case 5:
+ if(zversion <= 2)
+ {
+ current_alphabet = (current_alphabet + (c - 3)) % 3;
+ shift = 0;
+ }
+ else
+ {
+ shift = c - 3;
+ }
+ break;
+ case 6:
+ if(zversion <= 2) shift = (current_alphabet + shift) % 3;
+
+ if(shift == 2)
+ {
+ shift = 0;
+ special = 2;
+ break;
+ }
+ /* fallthrough */
+ default:
+ if(zversion <= 2 && c != 6) shift = (current_alphabet + shift) % 3;
+
+ outc(atable[(26 * shift) + (c - 6)]);
+ shift = 0;
+ break;
+ }
+ }
+
+ counter += 2;
+ } while((w & 0x8000) == 0);
+
+ return counter - addr;
+}
+
+/* Prints the string at addr “addr”.
+ *
+ * Returns the number of bytes the string took up. “outc” is passed as
+ * the character-print function to print_zcode(); if it is NULL,
+ * put_char is used.
+ */
+int print_handler(uint32_t addr, void (*outc)(uint8_t))
+{
+ return print_zcode(addr, 0, outc != NULL ? outc : put_char);
+}
+
+void zprint(void)
+{
+ pc += print_handler(pc, NULL);
+}
+
+void zprint_ret(void)
+{
+ zprint();
+ put_char(ZSCII_NEWLINE);
+ zrtrue();
+}
+
+void znew_line(void)
+{
+ put_char(ZSCII_NEWLINE);
+}
+
+void zerase_window(void)
+{
+#ifdef ZTERP_GLK
+ switch((int16_t)zargs[0])
+ {
+ case -2:
+ for(int i = 0; i < 8; i++) clear_window(&windows[i]);
+ break;
+ case -1:
+ close_upper_window();
+ /* fallthrough */
+ case 0:
+ /* 8.7.3.2.1 says V5+ should have the cursor set to 1, 1 of the
+ * erased window; V4 the lower window goes bottom left, the upper
+ * to 1, 1. Glk doesn’t give control over the cursor when
+ * clearing, and that doesn’t really seem to be an issue; so just
+ * call glk_window_clear().
+ */
+ clear_window(mainwin);
+ break;
+ case 1:
+ clear_window(upperwin);
+ break;
+ default:
+ show_message("@erase_window: unhandled window: %d", (int16_t)zargs[0]);
+ break;
+ }
+
+ /* glk_window_clear() kills reverse video in Gargoyle. Reapply style. */
+ set_current_style();
+#endif
+}
+
+void zerase_line(void)
+{
+#ifdef ZTERP_GLK
+ /* XXX V6 does pixel handling here. */
+ if(zargs[0] != 1 || curwin != upperwin || upperwin->id == NULL) return;
+
+ for(long i = upperwin->x; i < upper_window_width; i++) GLK_PUT_CHAR(UNICODE_SPACE);
+
+ glk_window_move_cursor(upperwin->id, upperwin->x, upperwin->y);
+#endif
+}
+
+/* XXX This is more complex in V6 and needs to be updated when V6 windowing is implemented. */
+static void set_cursor(uint16_t y, uint16_t x)
+{
+#ifdef ZTERP_GLK
+ /* All the windows in V6 can have their cursor positioned; if V6 ever
+ * comes about this should be fixed.
+ */
+ if(curwin != upperwin) return;
+
+ /* -1 and -2 are V6 only, but at least Zracer passes -1 (or it’s
+ * trying to position the cursor to line 65535; unlikely!)
+ */
+ if((int16_t)y == -1 || (int16_t)y == -2) return;
+
+ /* §8.7.2.3 says 1,1 is the top-left, but at least one program (Paint
+ * and Corners) uses @set_cursor 0 0 to go to the top-left; so
+ * special-case it.
+ */
+ if(y == 0) y = 1;
+ if(x == 0) x = 1;
+
+ /* This is actually illegal, but some games (e.g. Beyond Zork) expect it to work. */
+ if(y > upper_window_height) resize_upper_window(y);
+
+ if(upperwin->id != NULL)
+ {
+ upperwin->x = x - 1;
+ upperwin->y = y - 1;
+
+ glk_window_move_cursor(upperwin->id, x - 1, y - 1);
+ }
+#endif
+}
+
+void zset_cursor(void)
+{
+ set_cursor(zargs[0], zargs[1]);
+}
+
+void zget_cursor(void)
+{
+#ifdef ZTERP_GLK
+ user_store_word(zargs[0] + 0, upperwin->y + 1);
+ user_store_word(zargs[0] + 2, upperwin->x + 1);
+#else
+ user_store_word(zargs[0] + 0, 1);
+ user_store_word(zargs[0] + 2, 1);
+#endif
+}
+
+#ifndef ZTERP_GLK
+static int16_t fg_color = 1, bg_color = 1;
+#elif defined(GARGLK)
+static glui32 zcolor_map[] = {
+ zcolor_Default,
+
+ 0x000000, /* Black */
+ 0xef0000, /* Red */
+ 0x00d600, /* Green */
+ 0xefef00, /* Yellow */
+ 0x006bb5, /* Blue */
+ 0xff00ff, /* Magenta */
+ 0x00efef, /* Cyan */
+ 0xffffff, /* White */
+ 0xb5b5b5, /* Light grey */
+ 0x8c8c8c, /* Medium grey */
+ 0x5a5a5a, /* Dark grey */
+};
+static glui32 fg_color = zcolor_Default, bg_color = zcolor_Default;
+
+void update_color(int which, unsigned long color)
+{
+ if(which < 2 || which > 12) return;
+
+ zcolor_map[which - 1] = color;
+}
+#endif
+
+/* A window argument may be supplied in V6, and this needs to be implemented. */
+void zset_colour(void)
+{
+ /* Glk (apart from Gargoyle) has no color support. */
+#if !defined(ZTERP_GLK) || defined(GARGLK)
+ int16_t fg = zargs[0], bg = zargs[1];
+
+ /* In V6, each window has its own color settings. Since multiple
+ * windows are not supported, simply ignore all color requests except
+ * those in the main window.
+ */
+ if(zversion == 6 && curwin != mainwin) return;
+
+ if(options.disable_color) return;
+
+ /* XXX -1 is a valid color in V6. */
+#ifdef GARGLK
+ if(fg >= 1 && fg <= (zversion >= 5 ? 12 : 9)) fg_color = zcolor_map[fg - 1];
+ if(bg >= 1 && bg <= (zversion >= 5 ? 12 : 9)) bg_color = zcolor_map[bg - 1];
+
+#else
+ if(fg >= 1 && fg <= 9) fg_color = fg;
+ if(bg >= 1 && bg <= 9) bg_color = bg;
+#endif
+
+ set_current_style();
+#endif
+}
+
+#ifdef GARGLK
+/* Convert a 15-bit color to a 24-bit color. */
+static glui32 convert_color(unsigned long color)
+{
+ /* Map 5-bit color values to 8-bit. */
+ const uint8_t table[] = {
+ 0x00, 0x08, 0x10, 0x19, 0x21, 0x29, 0x31, 0x3a,
+ 0x42, 0x4a, 0x52, 0x5a, 0x63, 0x6b, 0x73, 0x7b,
+ 0x84, 0x8c, 0x94, 0x9c, 0xa5, 0xad, 0xb5, 0xbd,
+ 0xc5, 0xce, 0xd6, 0xde, 0xe6, 0xef, 0xf7, 0xff
+ };
+
+ return table[(color & 0x001f) >> 0] << 16 |
+ table[(color & 0x03e0) >> 5] << 8 |
+ table[(color & 0x7c00) >> 10] << 0;
+}
+#endif
+
+void zset_true_colour(void)
+{
+#ifdef GARGLK
+ long fg = (int16_t)zargs[0], bg = (int16_t)zargs[1];
+
+ if (fg >= 0) fg_color = convert_color(fg);
+ else if(fg == -1) fg_color = zcolor_Default;
+
+ if (bg >= 0) bg_color = convert_color(bg);
+ else if(bg == -1) bg_color = zcolor_Default;
+
+ set_current_style();
+#endif
+}
+
+int header_fixed_font;
+
+#ifdef GARGLK
+/* Idea from Nitfol. */
+static const int style_map[] =
+{
+ style_Normal,
+ style_Normal,
+
+ style_Subheader, /* Bold */
+ style_Subheader, /* Bold */
+ style_Emphasized, /* Italic */
+ style_Emphasized, /* Italic */
+ style_Alert, /* Bold Italic */
+ style_Alert, /* Bold Italic */
+ style_Preformatted, /* Fixed */
+ style_Preformatted, /* Fixed */
+ style_User1, /* Bold Fixed */
+ style_User1, /* Bold Fixed */
+ style_User2, /* Italic Fixed */
+ style_User2, /* Italic Fixed */
+ style_Note, /* Bold Italic Fixed */
+ style_Note, /* Bold Italic Fixed */
+};
+#endif
+
+/* Yes, there are three ways to indicate that a fixed-width font should be used. */
+#define use_fixed_font() (header_fixed_font || curwin->font == FONT_FIXED || (style & STYLE_FIXED))
+
+void set_current_style(void)
+{
+ unsigned style = style_window->style;
+#ifdef ZTERP_GLK
+ if(curwin->id == NULL) return;
+
+#ifdef GARGLK
+ if(use_fixed_font()) style |= STYLE_FIXED;
+
+ if(options.disable_fixed) style &= ~STYLE_FIXED;
+
+ ZASSERT(style < 16, "invalid style selected: %x", (unsigned)style);
+
+ glk_set_style(style_map[style]);
+
+ garglk_set_reversevideo(style & STYLE_REVERSE);
+
+ garglk_set_zcolors(fg_color, bg_color);
+#else
+ /* Glk can’t mix other styles with fixed-width, but the upper window
+ * is always fixed, so if it is selected, there is no need to
+ * explicitly request it here. In addition, the user can disable
+ * fixed-width fonts or tell Bocfel to assume that the output font is
+ * already fixed (e.g. in an xterm); in either case, there is no need
+ * to request a fixed font.
+ * This means that another style can also be applied if applicable.
+ */
+ if(use_fixed_font() &&
+ !options.disable_fixed &&
+ !options.assume_fixed &&
+ curwin != upperwin)
+ {
+ glk_set_style(style_Preformatted);
+ return;
+ }
+
+ /* According to standard 1.1, if mixed styles aren't available, the
+ * priority is Fixed, Italic, Bold, Reverse.
+ */
+ if (style & STYLE_ITALIC) glk_set_style(style_Emphasized);
+ else if(style & STYLE_BOLD) glk_set_style(style_Subheader);
+ else if(style & STYLE_REVERSE) glk_set_style(style_Alert);
+ else glk_set_style(style_Normal);
+#endif
+#else
+ zterp_os_set_style(style, fg_color, bg_color);
+#endif
+}
+
+#undef use_fixed_font
+
+/* V6 has per-window styles, but all others have a global style; in this
+ * case, track styles via the main window.
+ */
+void zset_text_style(void)
+{
+ /* A style of 0 means all others go off. */
+ if(zargs[0] == 0) style_window->style = STYLE_NONE;
+ else style_window->style |= zargs[0];
+
+ set_current_style();
+}
+
+/* Interpreters seem to disagree on @set_font. Given the code
+
+ @set_font 4 -> i;
+ @set_font 1 -> j;
+ @set_font 0 -> k;
+ @set_font 1 -> l;
+
+ * the following values are returned:
+ * Frotz 2.43: 0, 1, 1, 1
+ * Gargoyle r384: 1, 4, 4, 4
+ * Fizmo 0.6.5: 1, 4, 1, 0
+ * Nitfol 0.5: 1, 4, 0, 1
+ * Filfre .987: 1, 4, 0, 1
+ * Zoom 1.1.4: 1, 1, 0, 1
+ * ZLR 0.07: 0, 1, 0, 1
+ * Windows Frotz 1.15: 1, 4, 1, 1
+ * XZip 1.8.2: 0, 4, 0, 0
+ *
+ * The standard says that “ID 0 means ‘the previous font’.” (§8.1.2).
+ * The Frotz 2.43 source code says that “zargs[0] = number of font or 0
+ * to keep current font”.
+ *
+ * How to implement @set_font turns on the meaning of “previous”. Does
+ * it mean the previous font _after_ the @set_font call, meaning Frotz
+ * is right? Or is it the previous font _before_ the @set_font call,
+ * meaning the identity of two fonts needs to be tracked?
+ *
+ * Currently I do the latter. That yields the following:
+ * 1, 4, 1, 4
+ * Almost comically, no interpreters agree with each other.
+ */
+void zset_font(void)
+{
+ struct window *win = curwin;
+
+ if(zversion == 6 && znargs == 2 && (int16_t)zargs[1] != -3)
+ {
+ ZASSERT(zargs[1] < 8, "invalid window selected: %d", (int16_t)zargs[1]);
+ win = &windows[zargs[1]];
+ }
+
+ /* If no previous font has been stored, consider that an error. */
+ if(zargs[0] == FONT_PREVIOUS && win->prev_font != FONT_NONE)
+ {
+ zargs[0] = win->prev_font;
+ zset_font();
+ }
+ else if(zargs[0] == FONT_NORMAL ||
+ (zargs[0] == FONT_CHARACTER && !options.disable_graphics_font) ||
+ (zargs[0] == FONT_FIXED && !options.disable_fixed))
+ {
+ store(win->font);
+ win->prev_font = win->font;
+ win->font = zargs[0];
+ }
+ else
+ {
+ store(0);
+ }
+
+ set_current_style();
+}
+
+void zprint_table(void)
+{
+ uint16_t text = zargs[0], width = zargs[1], height = zargs[2], skip = zargs[3];
+ uint16_t n = 0;
+
+#ifdef ZTERP_GLK
+ uint16_t start = 0; /* initialize to appease gcc */
+
+ if(curwin == upperwin) start = upperwin->x + 1;
+#endif
+
+ if(znargs < 3) height = 1;
+ if(znargs < 4) skip = 0;
+
+ for(uint16_t i = 0; i < height; i++)
+ {
+ for(uint16_t j = 0; j < width; j++)
+ {
+ put_char(user_byte(text + n++));
+ }
+
+ if(i + 1 != height)
+ {
+ n += skip;
+#ifdef ZTERP_GLK
+ if(curwin == upperwin)
+ {
+ set_cursor(upperwin->y + 2, start);
+ }
+ else
+#endif
+ {
+ put_char(ZSCII_NEWLINE);
+ }
+ }
+ }
+}
+
+void zprint_char(void)
+{
+ /* Check 32 (space) first: a cursory examination of story files
+ * indicates that this is the most common value passed to @print_char.
+ * This appears to be due to V4+ games blanking the upper window.
+ */
+#define valid_zscii_output(c) ((c) == 32 || (c) == 0 || (c) == 9 || (c) == 11 || (c) == 13 || ((c) > 32 && (c) <= 126) || ((c) >= 155 && (c) <= 251))
+ ZASSERT(valid_zscii_output(zargs[0]), "@print_char called with invalid character: %u", (unsigned)zargs[0]);
+#undef valid_zscii_output
+
+ put_char(zargs[0]);
+}
+
+void zprint_num(void)
+{
+ char buf[7];
+ int i = 0;
+ long v = (int16_t)zargs[0];
+
+ if(v < 0) v = -v;
+
+ do
+ {
+ buf[i++] = '0' + (v % 10);
+ } while(v /= 10);
+
+ if((int16_t)zargs[0] < 0) buf[i++] = '-';
+
+ while(i--) put_char(buf[i]);
+}
+
+void zprint_addr(void)
+{
+ print_handler(zargs[0], NULL);
+}
+
+void zprint_paddr(void)
+{
+ print_handler(unpack(zargs[0], 1), NULL);
+}
+
+/* XXX This is more complex in V6 and needs to be updated when V6 windowing is implemented. */
+void zsplit_window(void)
+{
+ if(zargs[0] == 0) close_upper_window();
+ else resize_upper_window(zargs[0]);
+}
+
+void zset_window(void)
+{
+ set_current_window(find_window(zargs[0]));
+}
+
+#ifdef ZTERP_GLK
+static void window_change(void)
+{
+ /* When a textgrid (the upper window) in Gargoyle is rearranged, it
+ * forgets about reverse video settings, so reapply any styles to the
+ * current window (it doesn’t hurt if the window is a textbuffer). If
+ * the current window is not the upper window that’s OK, because
+ * set_current_style() is called when a @set_window is requested.
+ */
+ set_current_style();
+
+ /* If the new window is smaller, the cursor of the upper window might
+ * be out of bounds. Pull it back in if so.
+ */
+ if(zversion >= 3 && upperwin->id != NULL && upper_window_height > 0)
+ {
+ long x = upperwin->x, y = upperwin->y;
+ glui32 w, h;
+
+ glk_window_get_size(upperwin->id, &w, &h);
+
+ upper_window_width = w;
+ upper_window_height = h;
+
+ if(x > w) x = w;
+ if(y > h) y = h;
+
+ SWITCH_WINDOW_START(upperwin);
+ set_cursor(y + 1, x + 1);
+ SWITCH_WINDOW_END();
+ }
+
+ /* §8.4
+ * Only 0x20 and 0x21 are mentioned; what of 0x22 and 0x24? Zoom and
+ * Windows Frotz both update the V5 header entries, so do that here,
+ * too.
+ *
+ * Also, no version restrictions are given, but assume V4+ per §11.1.
+ */
+ if(zversion >= 4)
+ {
+ unsigned width, height;
+
+ get_screen_size(&width, &height);
+
+ STORE_BYTE(0x20, height > 254 ? 254 : height);
+ STORE_BYTE(0x21, width > 255 ? 255 : width);
+
+ if(zversion >= 5)
+ {
+ STORE_WORD(0x22, width > UINT16_MAX ? UINT16_MAX : width);
+ STORE_WORD(0x24, height > UINT16_MAX ? UINT16_MAX : height);
+ }
+ }
+ else
+ {
+ zshow_status();
+ }
+}
+#endif
+
+#ifdef ZTERP_GLK
+static int timer_running;
+
+static void start_timer(uint16_t n)
+{
+ if(!TIMER_AVAILABLE()) return;
+
+ if(timer_running) die("nested timers unsupported");
+ glk_request_timer_events(n * 100);
+ timer_running = 1;
+}
+
+static void stop_timer(void)
+{
+ if(!TIMER_AVAILABLE()) return;
+
+ glk_request_timer_events(0);
+ timer_running = 0;
+}
+
+static void request_char(void)
+{
+ if(have_unicode) glk_request_char_event_uni(curwin->id);
+ else glk_request_char_event(curwin->id);
+
+ curwin->pending_read = 1;
+}
+
+static void request_line(union line *line, glui32 maxlen, glui32 initlen)
+{
+ if(have_unicode) glk_request_line_event_uni(curwin->id, line->unicode, maxlen, initlen);
+ else glk_request_line_event(curwin->id, line->latin1, maxlen, initlen);
+
+ curwin->pending_read = 1;
+ curwin->line = line;
+}
+#endif
+
+#define special_zscii(c) ((c) >= 129 && (c) <= 154)
+
+/* This is called when input stream 1 (read from file) is selected. If
+ * it succefully reads a character/line from the file, it fills the
+ * struct at “input” with the appropriate information and returns true.
+ * If it fails to read (likely due to EOF) then it sets the input stream
+ * back to the keyboard and returns false.
+ */
+static int istream_read_from_file(struct input *input)
+{
+ if(input->type == INPUT_CHAR)
+ {
+ long c;
+
+ c = zterp_io_getc(istreamio);
+ if(c == -1)
+ {
+ input_stream(ISTREAM_KEYBOARD);
+ return 0;
+ }
+
+ /* Don’t translate special ZSCII characters (cursor keys, function keys, keypad). */
+ if(special_zscii(c)) input->key = c;
+ else input->key = unicode_to_zscii_q[c];
+ }
+ else
+ {
+ long n;
+ uint16_t line[1024];
+
+ n = zterp_io_readline(istreamio, line, sizeof line / sizeof *line);
+ if(n == -1)
+ {
+ input_stream(ISTREAM_KEYBOARD);
+ return 0;
+ }
+
+ if(n > input->maxlen) n = input->maxlen;
+
+ input->len = n;
+
+#ifdef ZTERP_GLK
+ if(curwin->id != NULL)
+ {
+ glk_set_style(style_Input);
+ for(long i = 0; i < n; i++) GLK_PUT_CHAR(line[i]);
+ GLK_PUT_CHAR(UNICODE_LINEFEED);
+ set_current_style();
+ }
+#else
+ for(long i = 0; i < n; i++) zterp_io_putc(zterp_io_stdout(), line[i]);
+ zterp_io_putc(zterp_io_stdout(), UNICODE_LINEFEED);
+#endif
+
+ for(long i = 0; i < n; i++) input->line[i] = line[i];
+ }
+
+#ifdef ZTERP_GLK
+ event_t ev;
+
+ /* It’s possible that output is buffered, meaning that until
+ * glk_select() is called, output will not be displayed. When reading
+ * from a command-script, flush on each command so that output is
+ * visible while the script is being replayed.
+ */
+ glk_select_poll(&ev);
+ switch(ev.type)
+ {
+ case evtype_None:
+ break;
+ case evtype_Arrange:
+ window_change();
+ break;
+ default:
+ /* No other events should arrive. Timers are only started in
+ * get_input() and are stopped before that function returns.
+ * Input events will not happen with glk_select_poll(), and no
+ * other event type is expected to be raised.
+ */
+ break;
+ }
+
+ saw_input = 1;
+#endif
+
+ return 1;
+}
+
+#ifdef GLK_MODULE_LINE_TERMINATORS
+/* Glk returns terminating characters as keycode_*, but we need them as
+ * ZSCII. This should only ever be called with values that are matched
+ * in the switch, because those are the only ones that Glk was told are
+ * terminating characters. In the event that another keycode comes
+ * through, though, treat it as Enter.
+ */
+static uint8_t zscii_from_glk(glui32 key)
+{
+ switch(key)
+ {
+ case 13: return ZSCII_NEWLINE;
+ case keycode_Up: return 129;
+ case keycode_Down: return 130;
+ case keycode_Left: return 131;
+ case keycode_Right: return 131;
+ case keycode_Func1: return 133;
+ case keycode_Func2: return 134;
+ case keycode_Func3: return 135;
+ case keycode_Func4: return 136;
+ case keycode_Func5: return 137;
+ case keycode_Func6: return 138;
+ case keycode_Func7: return 139;
+ case keycode_Func8: return 140;
+ case keycode_Func9: return 141;
+ case keycode_Func10: return 142;
+ case keycode_Func11: return 143;
+ case keycode_Func12: return 144;
+ }
+
+ return ZSCII_NEWLINE;
+}
+#endif
+
+#ifdef ZTERP_GLK
+/* This is like strlen() but in addition to C strings it can find the
+ * length of a Unicode string (which is assumed to be zero terminated)
+ * if Unicode is being used.
+ */
+static size_t line_len(const union line *line)
+{
+ size_t i;
+
+ if(!have_unicode) return strlen(line->latin1);
+
+ for(i = 0; line->unicode[i] != 0; i++)
+ {
+ }
+
+ return i;
+}
+#endif
+
+/* Attempt to read input from the user. The input type can be either a
+ * single character or a full line. If “timer” is not zero, a timer is
+ * started that fires off every “timer” tenths of a second (if the value
+ * is 1, it will timeout 10 times a second, etc.). Each time the timer
+ * times out the routine at address “routine” is called. If the routine
+ * returns true, the input is canceled.
+ *
+ * The function returns 1 if input was stored, 0 if there was a
+ * cancellation as described above.
+ */
+static int get_input(int16_t timer, int16_t routine, struct input *input)
+{
+ /* If either of these is zero, no timeout should happen. */
+ if(timer == 0) routine = 0;
+ if(routine == 0) timer = 0;
+
+ /* Flush all streams when input is requested. */
+#ifndef ZTERP_GLK
+ zterp_io_flush(zterp_io_stdout());
+#endif
+ zterp_io_flush(scriptio);
+ zterp_io_flush(transio);
+
+ /* Generally speaking, newline will be the reason the line input
+ * stopped, so set it by default. It will be overridden where
+ * necessary.
+ */
+ input->term = ZSCII_NEWLINE;
+
+ if(istream == ISTREAM_FILE && istream_read_from_file(input)) return 1;
+#ifdef ZTERP_GLK
+ int status = 0;
+ union line line;
+ struct window *saved = NULL;
+
+ /* In V6, input might be requested on an unsupported window. If so,
+ * switch to the main window temporarily.
+ */
+ if(curwin->id == NULL)
+ {
+ saved = curwin;
+ curwin = mainwin;
+ glk_set_window(curwin->id);
+ }
+
+ if(input->type == INPUT_CHAR)
+ {
+ request_char();
+ }
+ else
+ {
+ for(int i = 0; i < input->preloaded; i++)
+ {
+ if(have_unicode) line.unicode[i] = input->line[i];
+ else line.latin1 [i] = input->line[i];
+ }
+
+ request_line(&line, input->maxlen, input->preloaded);
+ }
+
+ if(timer != 0) start_timer(timer);
+
+ while(status == 0)
+ {
+ event_t ev;
+
+ glk_select(&ev);
+
+ switch(ev.type)
+ {
+ case evtype_Arrange:
+ window_change();
+ break;
+
+ case evtype_Timer:
+ {
+ ZASSERT(timer != 0, "got unexpected evtype_Timer");
+
+ struct window *saved2 = curwin;
+ int ret;
+
+ stop_timer();
+
+ ret = direct_call(routine);
+
+ /* It’s possible for an interrupt to switch windows; if it
+ * does, simply switch back. This is the easiest way to deal
+ * with an undefined bit of the Z-machine.
+ */
+ if(curwin != saved2) set_current_window(saved2);
+
+ if(ret)
+ {
+ status = 2;
+ }
+ else
+ {
+ /* If this got reset to 0, that means an interrupt had to
+ * cancel the read event in order to either read or write.
+ */
+ if(!curwin->pending_read)
+ {
+ if(input->type == INPUT_CHAR) request_char();
+ else request_line(&line, input->maxlen, line_len(&line));
+ }
+
+ start_timer(timer);
+ }
+ }
+
+ break;
+
+ case evtype_CharInput:
+ ZASSERT(input->type == INPUT_CHAR, "got unexpected evtype_CharInput");
+ ZASSERT(ev.win == curwin->id, "got evtype_CharInput on unexpected window");
+
+ status = 1;
+
+ switch(ev.val1)
+ {
+ case keycode_Delete: input->key = 8; break;
+ case keycode_Return: input->key = 13; break;
+ case keycode_Escape: input->key = 27; break;
+ case keycode_Up: input->key = 129; break;
+ case keycode_Down: input->key = 130; break;
+ case keycode_Left: input->key = 131; break;
+ case keycode_Right: input->key = 132; break;
+ case keycode_Func1: input->key = 133; break;
+ case keycode_Func2: input->key = 134; break;
+ case keycode_Func3: input->key = 135; break;
+ case keycode_Func4: input->key = 136; break;
+ case keycode_Func5: input->key = 137; break;
+ case keycode_Func6: input->key = 138; break;
+ case keycode_Func7: input->key = 139; break;
+ case keycode_Func8: input->key = 140; break;
+ case keycode_Func9: input->key = 141; break;
+ case keycode_Func10: input->key = 142; break;
+ case keycode_Func11: input->key = 143; break;
+ case keycode_Func12: input->key = 144; break;
+
+ default:
+ input->key = ZSCII_QUESTIONMARK;
+
+ if(ev.val1 <= UINT16_MAX)
+ {
+ uint8_t c = unicode_to_zscii[ev.val1];
+
+ if(c != 0) input->key = c;
+ }
+
+ break;
+ }
+
+ break;
+
+ case evtype_LineInput:
+ ZASSERT(input->type == INPUT_LINE, "got unexpected evtype_LineInput");
+ ZASSERT(ev.win == curwin->id, "got evtype_LineInput on unexpected window");
+ input->len = ev.val1;
+#ifdef GLK_MODULE_LINE_TERMINATORS
+ if(zversion >= 5) input->term = zscii_from_glk(ev.val2);
+#endif
+ status = 1;
+ break;
+ }
+ }
+
+ stop_timer();
+
+ if(input->type == INPUT_CHAR)
+ {
+ glk_cancel_char_event(curwin->id);
+ }
+ else
+ {
+ /* On cancellation, the buffer still needs to be filled, because
+ * it’s possible that line input echoing has been turned off and the
+ * contents will need to be written out.
+ */
+ if(status == 2)
+ {
+ event_t ev;
+
+ glk_cancel_line_event(curwin->id, &ev);
+ input->len = ev.val1;
+ input->term = 0;
+ }
+
+ for(glui32 i = 0; i < input->len; i++)
+ {
+ if(have_unicode) input->line[i] = line.unicode[i] > UINT16_MAX ? UNICODE_QUESTIONMARK : line.unicode[i];
+ else input->line[i] = (uint8_t)line.latin1[i];
+ }
+ }
+
+ curwin->pending_read = 0;
+ curwin->line = NULL;
+
+ if(status == 1) saw_input = 1;
+
+ if(errorwin != NULL)
+ {
+ glk_window_close(errorwin, NULL);
+ errorwin = NULL;
+ }
+
+ if(saved != NULL)
+ {
+ curwin = saved;
+ glk_set_window(curwin->id);
+ }
+
+ return status != 2;
+#else
+ if(input->type == INPUT_CHAR)
+ {
+ long n;
+ uint16_t line[64];
+
+ n = zterp_io_readline(zterp_io_stdin(), line, sizeof line / sizeof *line);
+
+ /* On error/eof, or if an invalid key was typed, pretend “Enter” was hit. */
+ if(n <= 0)
+ {
+ input->key = ZSCII_NEWLINE;
+ }
+ else
+ {
+ input->key = unicode_to_zscii[line[0]];
+ if(input->key == 0) input->key = ZSCII_NEWLINE;
+ }
+ }
+ else
+ {
+ input->len = input->preloaded;
+
+ if(input->maxlen > input->preloaded)
+ {
+ long n;
+ uint16_t line[1024];
+
+ n = zterp_io_readline(zterp_io_stdin(), line, sizeof line / sizeof *line);
+ if(n != -1)
+ {
+ if(n > input->maxlen - input->preloaded) n = input->maxlen - input->preloaded;
+ for(long i = 0; i < n; i++) input->line[i + input->preloaded] = line[i];
+ input->len += n;
+ }
+ }
+ }
+
+ return 1;
+#endif
+}
+
+void zread_char(void)
+{
+ uint16_t timer = 0;
+ uint16_t routine = zargs[2];
+ struct input input = { .type = INPUT_CHAR };
+
+#ifdef ZTERP_GLK
+ cancel_read_events(curwin);
+#endif
+
+ if(zversion >= 4 && znargs > 1) timer = zargs[1];
+
+ if(!get_input(timer, routine, &input))
+ {
+ store(0);
+ return;
+ }
+
+#ifdef ZTERP_GLK
+ update_delayed();
+#endif
+
+ if(streams & STREAM_SCRIPT)
+ {
+ /* Values 127 to 159 are not valid Unicode, and these just happen to
+ * match up to the values needed for special ZSCII keys, so store
+ * them as-is.
+ */
+ if(special_zscii(input.key)) zterp_io_putc(scriptio, input.key);
+ else zterp_io_putc(scriptio, zscii_to_unicode[input.key]);
+ }
+
+ store(input.key);
+}
+
+#ifdef ZTERP_GLK
+static void status_putc(uint8_t c)
+{
+ glk_put_char(zscii_to_unicode[c]);
+}
+#endif
+
+void zshow_status(void)
+{
+#ifdef ZTERP_GLK
+ glui32 width, height;
+ char rhs[64];
+ int first = variable(0x11), second = variable(0x12);
+
+ if(statuswin.id == NULL) return;
+
+ glk_window_clear(statuswin.id);
+
+ SWITCH_WINDOW_START(&statuswin);
+
+ glk_window_get_size(statuswin.id, &width, &height);
+
+#ifdef GARGLK
+ garglk_set_reversevideo(1);
+#else
+ glk_set_style(style_Alert);
+#endif
+ for(glui32 i = 0; i < width; i++) glk_put_char(ZSCII_SPACE);
+
+ glk_window_move_cursor(statuswin.id, 1, 0);
+
+ /* Variable 0x10 is global variable 1. */
+ print_object(variable(0x10), status_putc);
+
+ if(STATUS_IS_TIME())
+ {
+ snprintf(rhs, sizeof rhs, "Time: %d:%02d%s ", (first + 11) % 12 + 1, second, first < 12 ? "am" : "pm");
+ if(strlen(rhs) > width) snprintf(rhs, sizeof rhs, "%02d:%02d", first, second);
+ }
+ else
+ {
+ snprintf(rhs, sizeof rhs, "Score: %d Moves: %d ", first, second);
+ if(strlen(rhs) > width) snprintf(rhs, sizeof rhs, "%d/%d", first, second);
+ }
+
+ if(strlen(rhs) <= width)
+ {
+ glk_window_move_cursor(statuswin.id, width - strlen(rhs), 0);
+ glk_put_string(rhs);
+ }
+
+ SWITCH_WINDOW_END();
+#endif
+}
+
+/* This is strcmp() except that the first string is Unicode. */
+static int unicmp(const uint32_t *s1, const char *s2)
+{
+ while(*s1 != 0 && *s2 == *s1)
+ {
+ s1++;
+ s2++;
+ }
+
+ return *s1 - *s2;
+}
+
+uint32_t read_pc;
+
+/* Try to parse a meta command. Returns true if input should be
+ * restarted, false to indicate no more input is required. In most
+ * cases input will be required because the game has requested it, but
+ * /undo and /restore jump to different locations, so the current @read
+ * no longer exists.
+ */
+static int handle_meta_command(const uint32_t *string)
+{
+ if(unicmp(string, "undo") == 0)
+ {
+ uint16_t flags2 = WORD(0x10);
+ int success = pop_save();
+
+ if(success != 0)
+ {
+ /* §6.1.2. */
+ STORE_WORD(0x10, flags2);
+
+ if(zversion >= 5) store(success);
+ else put_string("[undone]\n\n>");
+
+ return 0;
+ }
+ else
+ {
+ put_string("[no save found, unable to undo]");
+ }
+ }
+ else if(unicmp(string, "scripton") == 0)
+ {
+ if(output_stream(OSTREAM_SCRIPT, 0)) put_string("[transcripting on]");
+ else put_string("[transcripting failed]");
+ }
+ else if(unicmp(string, "scriptoff") == 0)
+ {
+ output_stream(-OSTREAM_SCRIPT, 0);
+ put_string("[transcripting off]");
+ }
+ else if(unicmp(string, "recon") == 0)
+ {
+ if(output_stream(OSTREAM_RECORD, 0)) put_string("[command recording on]");
+ else put_string("[command recording failed]");
+ }
+ else if(unicmp(string, "recoff") == 0)
+ {
+ output_stream(-OSTREAM_RECORD, 0);
+ put_string("[command recording off]");
+ }
+ else if(unicmp(string, "replay") == 0)
+ {
+ if(input_stream(ISTREAM_FILE)) put_string("[replaying commands]");
+ else put_string("[replaying commands failed]");
+ }
+ else if(unicmp(string, "save") == 0)
+ {
+ if(interrupt_level() != 0)
+ {
+ put_string("[cannot call /save while in an interrupt]");
+ }
+ else
+ {
+ uint32_t tmp = pc;
+
+ /* pc is currently set to the next instruction, but the restore
+ * needs to come back to *this* instruction; so temporarily set
+ * pc back before saving.
+ */
+ pc = read_pc;
+ if(do_save(1)) put_string("[saved]");
+ else put_string("[save failed]");
+ pc = tmp;
+ }
+ }
+ else if(unicmp(string, "restore") == 0)
+ {
+ if(do_restore(1))
+ {
+ put_string("[restored]\n\n>");
+ return 0;
+ }
+ else
+ {
+ put_string("[restore failed]");
+ }
+ }
+ else if(unicmp(string, "help") == 0)
+ {
+ put_string(
+ "/undo: undo a turn\n"
+ "/scripton: start a transcript\n"
+ "/scriptoff: stop a transcript\n"
+ "/recon: start a command record\n"
+ "/recoff: stop a command record\n"
+ "/replay: replay a command record\n"
+ "/save: save the game\n"
+ "/restore: restore a game saved by /save"
+ );
+ }
+ else
+ {
+ put_string("[unknown command]");
+ }
+
+ return 1;
+}
+
+void zread(void)
+{
+ uint16_t text = zargs[0], parse = zargs[1];
+ uint8_t maxchars = zversion >= 5 ? user_byte(text) : user_byte(text) - 1;
+ uint8_t zscii_string[maxchars];
+ uint32_t string[maxchars + 1];
+ struct input input = { .type = INPUT_LINE, .line = string, .maxlen = maxchars };
+ uint16_t timer = 0;
+ uint16_t routine = zargs[3];
+
+#ifdef ZTERP_GLK
+ cancel_read_events(curwin);
+#endif
+
+ if(zversion <= 3) zshow_status();
+
+ if(zversion >= 4 && znargs > 2) timer = zargs[2];
+
+ if(zversion >= 5)
+ {
+ int i;
+
+ input.preloaded = user_byte(text + 1);
+ ZASSERT(input.preloaded <= maxchars, "too many preloaded characters: %d when max is %d", input.preloaded, maxchars);
+
+ for(i = 0; i < input.preloaded; i++) string[i] = zscii_to_unicode[user_byte(text + i + 2)];
+ string[i] = 0;
+
+ /* Under garglk, preloaded input works as it’s supposed to.
+ * Under Glk, it can fail one of two ways:
+ * 1. The preloaded text is printed out once, but is not editable.
+ * 2. The preloaded text is printed out twice, the second being editable.
+ * I have chosen option #2. For non-Glk, option #1 is done by necessity.
+ */
+#ifdef GARGLK
+ if(curwin->id != NULL) garglk_unput_string_uni(string);
+#endif
+ }
+
+ if(!get_input(timer, routine, &input))
+ {
+#ifdef ZTERP_GLK
+ cleanup_screen(&input);
+#endif
+ if(zversion >= 5) store(0);
+ return;
+ }
+
+#ifdef ZTERP_GLK
+ cleanup_screen(&input);
+#endif
+
+#ifdef ZTERP_GLK
+ update_delayed();
+#endif
+
+ if(options.enable_escape && (streams & STREAM_TRANS))
+ {
+ zterp_io_putc(transio, 033);
+ zterp_io_putc(transio, '[');
+ for(int i = 0; options.escape_string[i] != 0; i++) zterp_io_putc(transio, options.escape_string[i]);
+ }
+
+ for(int i = 0; i < input.len; i++)
+ {
+ zscii_string[i] = unicode_to_zscii_q[unicode_tolower(string[i])];
+ if(streams & STREAM_TRANS) zterp_io_putc(transio, string[i]);
+ if(streams & STREAM_SCRIPT) zterp_io_putc(scriptio, string[i]);
+ }
+
+ if(options.enable_escape && (streams & STREAM_TRANS))
+ {
+ zterp_io_putc(transio, 033);
+ zterp_io_putc(transio, '[');
+ zterp_io_putc(transio, '0');
+ zterp_io_putc(transio, 'm');
+ }
+
+ if(streams & STREAM_TRANS) zterp_io_putc(transio, UNICODE_LINEFEED);
+ if(streams & STREAM_SCRIPT) zterp_io_putc(scriptio, UNICODE_LINEFEED);
+
+ if(!options.disable_meta_commands)
+ {
+ string[input.len] = 0;
+
+ if(string[0] == '/')
+ {
+ if(handle_meta_command(string + 1))
+ {
+ /* The game still wants input, so try again. */
+ put_string("\n\n>");
+ zread();
+ }
+
+ return;
+ }
+
+ /* V1–4 do not have @save_undo, so simulate one each time @read is
+ * called.
+ *
+ * pc is currently set to the next instruction, but the undo needs
+ * to come back to *this* instruction; so temporarily set pc back
+ * before pushing the save.
+ */
+ if(zversion <= 4)
+ {
+ uint32_t tmp_pc = pc;
+
+ pc = read_pc;
+ push_save();
+ pc = tmp_pc;
+ }
+ }
+
+ if(zversion >= 5)
+ {
+ user_store_byte(text + 1, input.len); /* number of characters read */
+
+ for(int i = 0; i < input.len; i++)
+ {
+ user_store_byte(text + i + 2, zscii_string[i]);
+ }
+
+ if(parse != 0) tokenize(text, parse, 0, 0);
+
+ store(input.term);
+ }
+ else
+ {
+ for(int i = 0; i < input.len; i++)
+ {
+ user_store_byte(text + i + 1, zscii_string[i]);
+ }
+
+ user_store_byte(text + input.len + 1, 0);
+
+ tokenize(text, parse, 0, 0);
+ }
+}
+
+void zprint_unicode(void)
+{
+ if(valid_unicode(zargs[0])) put_char_u(zargs[0]);
+ else put_char_u(UNICODE_QUESTIONMARK);
+}
+
+void zcheck_unicode(void)
+{
+ uint16_t res = 0;
+
+ /* valid_unicode() will tell which Unicode characters can be printed;
+ * and if the Unicode character is in the Unicode input table, it can
+ * also be read. If Unicode is not available, then any character >255
+ * is invalid for both reading and writing.
+ */
+ if(have_unicode || zargs[0] < 256)
+ {
+ if(valid_unicode(zargs[0])) res |= 0x01;
+ if(unicode_to_zscii[zargs[0]] != 0) res |= 0x02;
+ }
+
+ store(res);
+}
+
+/* Should picture_data and get_wind_prop be moved to a V6 source file? */
+void zpicture_data(void)
+{
+ if(zargs[0] == 0)
+ {
+ user_store_word(zargs[1] + 0, 0);
+ user_store_word(zargs[1] + 2, 0);
+ }
+
+ /* No pictures means no valid pictures, so never branch. */
+ branch_if(0);
+}
+
+void zget_wind_prop(void)
+{
+ uint16_t val;
+ struct window *win;
+
+ win = find_window(zargs[0]);
+
+ /* These are mostly bald-faced lies. */
+ switch(zargs[1])
+ {
+ case 0: /* y coordinate */
+ val = 0;
+ break;
+ case 1: /* x coordinate */
+ val = 0;
+ break;
+ case 2: /* y size */
+ val = 100;
+ break;
+ case 3: /* x size */
+ val = 100;
+ break;
+ case 4: /* y cursor */
+ val = 0;
+ break;
+ case 5: /* x cursor */
+ val = 0;
+ break;
+ case 6: /* left margin size */
+ val = 0;
+ break;
+ case 7: /* right margin size */
+ val = 0;
+ break;
+ case 8: /* newline interrupt routine */
+ val = 0;
+ break;
+ case 9: /* interrupt countdown */
+ val = 0;
+ break;
+ case 10: /* text style */
+ val = win->style;
+ break;
+ case 11: /* colour data */
+ val = (9 << 8) | 2;
+ break;
+ case 12: /* font number */
+ val = win->font;
+ break;
+ case 13: /* font size */
+ val = (10 << 8) | 10;
+ break;
+ case 14: /* attributes */
+ val = 0;
+ break;
+ case 15: /* line count */
+ val = 0;
+ break;
+ case 16: /* true foreground colour */
+ val = 0;
+ break;
+ case 17: /* true background colour */
+ val = 0;
+ break;
+ default:
+ die("unknown window property: %u", (unsigned)zargs[1]);
+ }
+
+ store(val);
+}
+
+/* This is not correct, because @output_stream does not work as it
+ * should with a width argument; however, this does print out the
+ * contents of a table that was sent to stream 3, so it’s at least
+ * somewhat useful.
+ *
+ * Output should be to the currently-selected window, but since V6 is
+ * only marginally supported, other windows are not active. Send to the
+ * main window for the time being.
+ */
+void zprint_form(void)
+{
+ SWITCH_WINDOW_START(mainwin);
+
+ for(uint16_t i = 0; i < user_word(zargs[0]); i++)
+ {
+ put_char(user_byte(zargs[0] + 2 + i));
+ }
+
+ put_char(ZSCII_NEWLINE);
+
+ SWITCH_WINDOW_END();
+}
+
+void zmake_menu(void)
+{
+ branch_if(0);
+}
+
+void zbuffer_screen(void)
+{
+ store(0);
+}
+
+#ifdef GARGLK
+/* Glk does not guarantee great control over how various styles are
+ * going to look, but Gargoyle does. Abusing the Glk “style hints”
+ * functions allows for quite fine-grained control over style
+ * appearance. First, clear the (important) attributes for each style,
+ * and then recreate each in whatever mold is necessary. Re-use some
+ * that are expected to be correct (emphasized for italic, subheader for
+ * bold, and so on).
+ */
+static void set_default_styles(void)
+{
+ int styles[] = { style_Subheader, style_Emphasized, style_Alert, style_Preformatted, style_User1, style_User2, style_Note };
+
+ for(int i = 0; i < 7; i++)
+ {
+ glk_stylehint_set(wintype_AllTypes, styles[i], stylehint_Size, 0);
+ glk_stylehint_set(wintype_AllTypes, styles[i], stylehint_Weight, 0);
+ glk_stylehint_set(wintype_AllTypes, styles[i], stylehint_Oblique, 0);
+
+ /* This sets wintype_TextGrid to be proportional, which of course is
+ * wrong; but text grids are required to be fixed, so Gargoyle
+ * simply ignores this hint for those windows.
+ */
+ glk_stylehint_set(wintype_AllTypes, styles[i], stylehint_Proportional, 1);
+ }
+}
+#endif
+
+int create_mainwin(void)
+{
+#ifdef ZTERP_GLK
+
+#ifdef GARGLK
+ set_default_styles();
+
+ /* Bold */
+ glk_stylehint_set(wintype_AllTypes, style_Subheader, stylehint_Weight, 1);
+
+ /* Italic */
+ glk_stylehint_set(wintype_AllTypes, style_Emphasized, stylehint_Oblique, 1);
+
+ /* Bold Italic */
+ glk_stylehint_set(wintype_AllTypes, style_Alert, stylehint_Weight, 1);
+ glk_stylehint_set(wintype_AllTypes, style_Alert, stylehint_Oblique, 1);
+
+ /* Fixed */
+ glk_stylehint_set(wintype_AllTypes, style_Preformatted, stylehint_Proportional, 0);
+
+ /* Bold Fixed */
+ glk_stylehint_set(wintype_AllTypes, style_User1, stylehint_Weight, 1);
+ glk_stylehint_set(wintype_AllTypes, style_User1, stylehint_Proportional, 0);
+
+ /* Italic Fixed */
+ glk_stylehint_set(wintype_AllTypes, style_User2, stylehint_Oblique, 1);
+ glk_stylehint_set(wintype_AllTypes, style_User2, stylehint_Proportional, 0);
+
+ /* Bold Italic Fixed */
+ glk_stylehint_set(wintype_AllTypes, style_Note, stylehint_Weight, 1);
+ glk_stylehint_set(wintype_AllTypes, style_Note, stylehint_Oblique, 1);
+ glk_stylehint_set(wintype_AllTypes, style_Note, stylehint_Proportional, 0);
+#endif
+
+ mainwin->id = glk_window_open(0, 0, 0, wintype_TextBuffer, 1);
+ if(mainwin->id == NULL) return 0;
+ glk_set_window(mainwin->id);
+
+#ifdef GLK_MODULE_LINE_ECHO
+ mainwin->has_echo = glk_gestalt(gestalt_LineInputEcho, 0);
+ if(mainwin->has_echo) glk_set_echo_line_event(mainwin->id, 0);
+#endif
+
+ return 1;
+#else
+ return 1;
+#endif
+}
+
+int create_statuswin(void)
+{
+#ifdef ZTERP_GLK
+ if(statuswin.id == NULL) statuswin.id = glk_window_open(mainwin->id, winmethod_Above | winmethod_Fixed, 1, wintype_TextGrid, 0);
+ return statuswin.id != NULL;
+#else
+ return 0;
+#endif
+}
+
+int create_upperwin(void)
+{
+#ifdef ZTERP_GLK
+ /* On a restart, this function will get called again. It would be
+ * possible to try to resize the upper window to 0 if it already
+ * exists, but it’s easier to just destroy and recreate it.
+ */
+ if(upperwin->id != NULL) glk_window_close(upperwin->id, NULL);
+
+ /* The upper window appeared in V3. */
+ if(zversion >= 3)
+ {
+ upperwin->id = glk_window_open(mainwin->id, winmethod_Above | winmethod_Fixed, 0, wintype_TextGrid, 0);
+ upperwin->x = upperwin->y = 0;
+ upper_window_height = 0;
+
+ if(upperwin->id != NULL)
+ {
+ glui32 w, h;
+
+ glk_window_get_size(upperwin->id, &w, &h);
+ upper_window_width = w;
+
+ if(h != 0 || upper_window_width == 0)
+ {
+ glk_window_close(upperwin->id, NULL);
+ upperwin->id = NULL;
+ }
+ }
+ }
+
+ return upperwin->id != NULL;
+#else
+ return 0;
+#endif
+}
+
+void init_screen(void)
+{
+ for(int i = 0; i < 8; i++)
+ {
+ windows[i].style = STYLE_NONE;
+ windows[i].font = FONT_NORMAL;
+ windows[i].prev_font = FONT_NONE;
+
+#ifdef ZTERP_GLK
+ clear_window(&windows[i]);
+#ifdef GLK_MODULE_LINE_TERMINATORS
+ if(windows[i].id != NULL && term_nkeys != 0 && glk_gestalt(gestalt_LineTerminators, 0)) glk_set_terminators_line_event(windows[i].id, term_keys, term_nkeys);
+#endif
+#endif
+ }
+
+ close_upper_window();
+
+#ifdef ZTERP_GLK
+ if(statuswin.id != NULL) glk_window_clear(statuswin.id);
+
+ if(errorwin != NULL)
+ {
+ glk_window_close(errorwin, NULL);
+ errorwin = NULL;
+ }
+
+ stop_timer();
+
+#ifdef GARGLK
+ fg_color = zcolor_Default;
+ bg_color = zcolor_Default;
+#endif
+
+#else
+ fg_color = 1;
+ bg_color = 1;
+#endif
+
+ if(scriptio != NULL) zterp_io_close(scriptio);
+ scriptio = NULL;
+
+ input_stream(ISTREAM_KEYBOARD);
+
+ streams = STREAM_SCREEN;
+ stablei = -1;
+ set_current_window(mainwin);
+}