/* GlkAPI -- a Javascript Glk API for IF interfaces * GlkOte Library: version 2.2.3. * Glk API which this implements: version 0.7.4. * Designed by Andrew Plotkin * * * This Javascript library is copyright 2010-16 by Andrew Plotkin. * It is distributed under the MIT license; see the "LICENSE" file. * * This file is a Glk API compatibility layer for glkote.js. It offers a * set of Javascript calls which closely match the original C Glk API; * these work by means of glkote.js operations. * * This API was built for Quixe, which is a pure-Javascript Glulx * interpreter. Therefore, the API is a little strange. Notably, it * accepts text buffers in the form of arrays of integers, not * Javascript strings. Only the Glk calls that explicitly use strings * (glk_put_string, etc) accept Javascript native strings. * * If you are writing an application in pure Javascript, you can use * this layer (along with glkote.js). If you are writing a web app which * is the front face of a server-side Glk app, ignore this file -- use * glkote.js directly. */ /* Known problems: Some places in the library get confused about Unicode characters beyond 0xFFFF. They are handled correctly by streams, but grid windows will think they occupy two characters rather than one, which will throw off the grid spacing. Also, the glk_put_jstring() function can't handle them at all. Quixe printing operations that funnel through glk_put_jstring() -- meaning, most native string printing -- will break up three-byte characters into a UTF-16-encoded pair of two-byte characters. This will come out okay in a buffer window, but it will again mess up grid windows, and will also double the write-count in a stream. */ /* Put everything inside the Glk namespace. */ Glk = function() { /* The VM interface object. */ var VM = null; /* References to external libraries */ var Dialog; var GiDispa; var GiLoad; var GlkOte; /* Environment capabilities */ var support = {}; /* Options from the vm_options object. */ var option_exit_warning; var option_do_vm_autosave; var option_before_select_hook; var option_extevent_hook; var option_glk_gestalt_hook; /* Library display state. */ var has_exited = false; var ui_disabled = false; var ui_specialinput = null; var ui_specialcallback = null; var event_generation = 0; var current_partial_inputs = null; var current_partial_outputs = null; // Set external variable references function set_references( vm_options ) { if ( vm_options.Dialog ) { Dialog = vm_options.Dialog; } if ( !Dialog ) { if ( typeof window !== 'undefined' && window.Dialog ) { Dialog = window.Dialog; } else { throw new Error( 'No reference to Dialog' ); } } if ( vm_options.GiDispa ) { GiDispa = vm_options.GiDispa; } else if ( !GiDispa && typeof window !== 'undefined' && window.GiDispa ) { GiDispa = window.GiDispa; } if ( vm_options.GiLoad ) { GiLoad = vm_options.GiLoad; } else if ( !GiLoad && typeof window !== 'undefined' && window.GiLoad ) { GiLoad = window.GiLoad; } if ( vm_options.GlkOte ) { GlkOte = vm_options.GlkOte; } if ( !GlkOte ) { if ( typeof window !== 'undefined' && window.GlkOte ) { GlkOte = window.GlkOte; } else { throw new Error('No reference to GlkOte'); } } } /* Initialize the library, initialize the VM, and set it running. (It will run until the first glk_select() or glk_exit() call.) The vm_options argument must have a vm_options.vm field, which must be an appropriate VM interface object. (For example, Quixe.) This must have init() and resume() methods. The vm_options argument is also passed through to GlkOte as the game interface object. It can be used to affect some GlkOte display options, such as window spacing. (You do not need to provide a vm_options.accept() function. The Glk library sets that up for you.) */ function init(vm_options) { /* Set references to external libraries */ set_references( vm_options ); VM = vm_options.vm; if (GiDispa) GiDispa.set_vm(VM); vm_options.accept = accept_ui_event; GlkOte.init(vm_options); option_exit_warning = vm_options.exit_warning; option_do_vm_autosave = vm_options.do_vm_autosave; option_before_select_hook = vm_options.before_select_hook; option_extevent_hook = vm_options.extevent_hook; option_glk_gestalt_hook = vm_options.glk_gestalt_hook; if (option_before_select_hook) { option_before_select_hook(); } } function accept_ui_event(obj) { var box; //qlog("### accept_ui_event: " + obj.type + ", gen " + obj.gen); if (ui_disabled) { /* We've hit glk_exit() or a VM fatal error, or just blocked the UI for some modal dialog. */ qlog("### ui is disabled, ignoring event"); return; } if (obj.gen != event_generation) { GlkOte.log('Input event had wrong generation number: got ' + obj.gen + ', currently at ' + event_generation); return; } event_generation += 1; /* Note any partial inputs; we'll need them if the game cancels a line input. This may be undef. */ current_partial_inputs = obj.partial; switch (obj.type) { case 'init': content_metrics = obj.metrics; /* Process the support array */ if (obj.support) { obj.support.forEach(function(item) {support[item] = 1;}); } VM.init(); break; case 'external': var res = null; if (option_extevent_hook) { res = option_extevent_hook(obj.value); } if (!res && obj.value == 'timer') { /* Timer events no longer come in this way, but we'll still accept them. */ gli_timer_started = Date.now(); res = { type: Const.evtype_Timer }; } if (res && res.type) { handle_external_input(res); } break; case 'timer': gli_timer_started = Date.now(); var res = { type: Const.evtype_Timer }; handle_external_input(res); break; case 'hyperlink': handle_hyperlink_input(obj.window, obj.value); break; case 'mouse': handle_mouse_input(obj.window, obj.x, obj.y); break; case 'char': handle_char_input(obj.window, obj.value); break; case 'line': handle_line_input(obj.window, obj.value, obj.terminator); break; case 'arrange': content_metrics = obj.metrics; box = { left: content_metrics.outspacingx, top: content_metrics.outspacingy, right: content_metrics.width-content_metrics.outspacingx, bottom: content_metrics.height-content_metrics.outspacingy }; if (gli_rootwin) gli_window_rearrange(gli_rootwin, box); handle_arrange_input(); break; case 'redraw': handle_redraw_input(); break; case 'specialresponse': if (obj.response == 'fileref_prompt') { gli_fileref_create_by_prompt_callback(obj); } break; } } function handle_arrange_input() { if (!gli_selectref) return; gli_selectref.set_field(0, Const.evtype_Arrange); gli_selectref.set_field(1, null); gli_selectref.set_field(2, 0); gli_selectref.set_field(3, 0); if (GiDispa) GiDispa.prepare_resume(gli_selectref); gli_selectref = null; VM.resume(); } function handle_redraw_input() { if (!gli_selectref) return; gli_selectref.set_field(0, Const.evtype_Redraw); gli_selectref.set_field(1, null); gli_selectref.set_field(2, 0); gli_selectref.set_field(3, 0); if (GiDispa) GiDispa.prepare_resume(gli_selectref); gli_selectref = null; VM.resume(); } function handle_external_input(res) { if (!gli_selectref) return; /* This also handles timer input. */ var val1 = 0; var val2 = 0; if (res.val1) val1 = res.val1; if (res.val2) val2 = res.val2; gli_selectref.set_field(0, res.type); gli_selectref.set_field(1, null); gli_selectref.set_field(2, val1); gli_selectref.set_field(3, val2); if (GiDispa) GiDispa.prepare_resume(gli_selectref); gli_selectref = null; VM.resume(); } function handle_hyperlink_input(disprock, val) { if (!gli_selectref) return; var win = null; for (win=gli_windowlist; win; win=win.next) { if (win.disprock == disprock) break; } if (!win || !win.hyperlink_request) return; gli_selectref.set_field(0, Const.evtype_Hyperlink); gli_selectref.set_field(1, win); gli_selectref.set_field(2, val); gli_selectref.set_field(3, 0); win.hyperlink_request = false; if (GiDispa) GiDispa.prepare_resume(gli_selectref); gli_selectref = null; VM.resume(); } function handle_mouse_input(disprock, xpos, ypos) { if (!gli_selectref) return; var win = null; for (win=gli_windowlist; win; win=win.next) { if (win.disprock == disprock) break; } if (!win || !win.mouse_request) return; gli_selectref.set_field(0, Const.evtype_MouseInput); gli_selectref.set_field(1, win); gli_selectref.set_field(2, xpos); gli_selectref.set_field(3, ypos); win.mouse_request = false; if (GiDispa) GiDispa.prepare_resume(gli_selectref); gli_selectref = null; VM.resume(); } function handle_char_input(disprock, input) { var charval; if (!gli_selectref) return; var win = null; for (win=gli_windowlist; win; win=win.next) { if (win.disprock == disprock) break; } if (!win || !win.char_request) return; if (input.length == 1) { charval = input.charCodeAt(0); if (!win.char_request_uni) charval = charval & 0xFF; } else { charval = KeystrokeNameMap[input]; if (!charval) charval = Const.keycode_Unknown; } gli_selectref.set_field(0, Const.evtype_CharInput); gli_selectref.set_field(1, win); gli_selectref.set_field(2, charval); gli_selectref.set_field(3, 0); win.char_request = false; win.char_request_uni = false; win.input_generation = null; if (GiDispa) GiDispa.prepare_resume(gli_selectref); gli_selectref = null; VM.resume(); } function handle_line_input(disprock, input, termkey) { var ix; if (!gli_selectref) return; var win = null; for (win=gli_windowlist; win; win=win.next) { if (win.disprock == disprock) break; } if (!win || !win.line_request) return; if (input.length > win.linebuf.length) input = input.slice(0, win.linebuf.length); if (win.request_echo_line_input) { ix = win.style; gli_set_style(win.str, Const.style_Input); gli_window_put_string(win, input); if (win.echostr) glk_put_jstring_stream(win.echostr, input); gli_set_style(win.str, ix); gli_window_put_string(win, "\n"); if (win.echostr) glk_put_jstring_stream(win.echostr, "\n"); } for (ix=0; ix 100) { win.reserve.splice(0, win.reserve.length-100); } break; case Const.wintype_TextGrid: if (win.gridwidth == 0 || win.gridheight == 0) break; obj.lines = []; for (ix=0; ix= 0) { /* We're going to delete every command before the fill, except that we save the last setcolor. */ var setcol = null; for (ix=0; ix=0; ix--) { var obj = res.windows[ix]; var win = { type: obj.type, rock: obj.rock, disprock: obj.disprock, style: obj.style, hyperlink: obj.hyperlink }; GiDispa.class_register('window', win, win.disprock); win.prev = null; win.next = gli_windowlist; gli_windowlist = win; if (win.next) win.next.prev = win; } for (var ix=res.streams.length-1; ix>=0; ix--) { var obj = res.streams[ix]; var str = { type: obj.type, rock: obj.rock, disprock: obj.disprock, unicode: obj.unicode, isbinary: obj.isbinary, readcount: obj.readcount, writecount: obj.writecount, readable: obj.readable, writable: obj.writable, streaming: obj.streaming }; GiDispa.class_register('stream', str, str.disprock); str.prev = null; str.next = gli_streamlist; gli_streamlist = str; if (str.next) str.next.prev = str; } for (var ix=res.filerefs.length-1; ix>=0; ix--) { var obj = res.filerefs[ix]; var fref = { type: obj.type, rock: obj.rock, disprock: obj.disprock, filename: obj.filename, textmode: obj.textmode, filetype: obj.filetype, filetypename: obj.filetypename }; GiDispa.class_register('fileref', fref, fref.disprock); fref.prev = null; fref.next = gli_filereflist; gli_filereflist = fref; if (fref.next) fref.next.prev = fref; } /* ...Now we fill in the cross-references. */ for (var ix=0; ix str.bufeof) str.bufpos = str.bufeof; } else { str.ref = obj.ref; str.fstream = Dialog.file_fopen(str.origfmode, str.ref); if (!str.fstream) { /* This is the panic case. We can't reopen the stream, but the game expects an open stream! We'll just have to open a temporary file; the user will never get their data, but at least the game won't crash. (Better policy would be to prompt the user for a new file location...) */ var tempref = Dialog.file_construct_temp_ref(str.ref.usage); str.fstream = Dialog.file_fopen(str.origfmode, tempref); if (!str.fstream) throw('restore_allstate: could not reopen even a temp stream for: ' + str.ref.filename); } if (str.origfmode != Const.filemode_WriteAppend) { /* Jump to the last known filepos. */ str.fstream.fseek(obj.filepos, Const.seekmode_Start); } str.buffer4 = str.fstream.BufferClass.alloc(4); } break; } } for (var ix=0; ix> 10), 0xDC00 + (val & 0x3FF)); } } /* Given an array, return an array of the same length with all the values trimmed to the range 0-255. This may be the same array. */ function TrimArrayToBytes(arr) { var ix, newarr; var len = arr.length; for (ix=0; ix= 0x100) break; } if (ix == len) { return arr; } newarr = Array(len); for (ix=0; ix= 0x100) newarr[ix] = 63; // '?' else newarr[ix] = arr[ix]; } return newarr; } /* Convert an array of 8-bit values to a JS string, trimming if necessary. */ function ByteArrayToString(arr) { var ix, newarr; var len = arr.length; if (len == 0) return ''; for (ix=0; ix= 0x100) break; } if (ix == len) { return String.fromCharCode.apply(this, arr); } newarr = Array(len); for (ix=0; ix= 0x10000) break; } if (ix == len) { return String.fromCharCode.apply(this, arr); } newarr = Array(len); for (ix=0; ix> 10), 0xDC00 + (val & 0x3FF)); } } return newarr.join(''); } /* Convert an array of 32-bit Unicode values to an array of 8-bit byte values, encoded UTF-8. If all the values are 0-127, this returns the same array. Otherwise it returns a new (longer) array. */ function UniArrayToUTF8(arr) { var count = 0; for (var ix=0; ix> 6)); res.push(0x80 | (val & 0x03F) ); } else if (val < 0x10000) { res.push(0xE0 | ((val & 0xF000) >> 12)); res.push(0x80 | ((val & 0x0FC0) >> 6)); res.push(0x80 | (val & 0x003F) ); } else if (val < 0x200000) { res.push(0xF0 | ((val & 0x1C0000) >> 18)); res.push(0x80 | ((val & 0x03F000) >> 12)); res.push(0x80 | ((val & 0x000FC0) >> 6)); res.push(0x80 | (val & 0x00003F) ); } else { res.push(63); // '?' } } return res; } /* Convert an array of 32-bit Unicode values to an array of 8-bit byte values, encoded as big-endian words. */ function UniArrayToBE32(arr) { var res = new Array(4*arr.length); for (var ix=0; ix> 24) & 0xFF; res[4*ix+1] = (val >> 16) & 0xFF; res[4*ix+2] = (val >> 8) & 0xFF; res[4*ix+3] = (val) & 0xFF; } return res; } /* Log the message in the browser's error log, if it has one. (This shows up in Safari, in Opera, and in Firefox if you have Firebug installed.) */ function qlog(msg) { if (typeof console !== 'undefined' && console.log) console.log(msg); } /* RefBox: Simple class used for "call-by-reference" Glk arguments. The object is just a box containing a single value, which can be written and read. */ function RefBox() { this.value = undefined; this.set_value = function(val) { this.value = val; } this.get_value = function() { return this.value; } } /* RefStruct: Used for struct-type Glk arguments. After creating the object, you should call push_field() the appropriate number of times, to set the initial field values. Then set_field() can be used to change them, and get_fields() retrieves the list of all fields. (The usage here is loose, since Javascript is forgiving about arrays. Really the caller could call set_field() instead of push_field() -- or skip that step entirely, as long as the Glk function later calls set_field() for each field. Which it should.) */ function RefStruct(numels) { this.fields = []; this.push_field = function(val) { this.fields.push(val); } this.set_field = function(pos, val) { this.fields[pos] = val; } this.get_field = function(pos) { return this.fields[pos]; } this.get_fields = function() { return this.fields; } } /* Dummy return value, which means that the Glk call is still in progress, or will never return at all. This is used by glk_exit(), glk_select(), and glk_fileref_create_by_prompt(). */ var DidNotReturn = { dummy: 'Glk call has not yet returned' }; /* This returns a hint for whether the Glk call (by selector number) might block or never return. True for glk_exit(), glk_select(), and glk_fileref_create_by_prompt(). */ function call_may_not_return(id) { if (id == 0x001 || id == 0x0C0 || id == 0x062) return true; else return false; } var strtype_File = 1; var strtype_Window = 2; var strtype_Memory = 3; var strtype_Resource = 4; /* Extra update information -- autorestore only. */ var gli_autorestore_glkstate = null; /* Beginning of linked list of windows. */ var gli_windowlist = null; var gli_rootwin = null; /* Set when any window is created, destroyed, or resized. */ var geometry_changed = true; /* Received from GlkOte; describes the window size. */ var content_metrics = null; /* Beginning of linked list of streams. */ var gli_streamlist = null; /* Beginning of linked list of filerefs. */ var gli_filereflist = null; /* Beginning of linked list of schannels. */ var gli_schannellist = null; /* The current output stream. */ var gli_currentstr = null; /* During a glk_select() block, this is the RefStruct which will contain the result. */ var gli_selectref = null; /* This is used to assigned disprock values to windows, when there is no GiDispa layer to provide them. */ var gli_api_display_rocks = 1; /* A positive number if the timer is set. */ var gli_timer_interval = null; var gli_timer_started = null; /* when the setTimeout began */ var gli_timer_lastsent = null; /* last interval sent to GlkOte */ function gli_new_window(type, rock) { var win = {}; win.type = type; win.rock = rock; win.disprock = undefined; win.parent = null; win.str = gli_stream_open_window(win); win.echostr = null; win.style = Const.style_Normal; win.hyperlink = 0; win.input_generation = null; win.linebuf = null; win.char_request = false; win.line_request = false; win.char_request_uni = false; win.line_request_uni = false; win.hyperlink_request = false; win.mouse_request = false; win.echo_line_input = true; win.line_input_terminators = []; win.request_echo_line_input = null; /* only used during a request */ /* window-type-specific info is set up in glk_window_open */ win.prev = null; win.next = gli_windowlist; gli_windowlist = win; if (win.next) win.next.prev = win; if (GiDispa) GiDispa.class_register('window', win); else win.disprock = gli_api_display_rocks++; /* We need to assign a disprock even if there's no GiDispa layer, because GlkOte differentiates windows by their disprock. */ geometry_changed = true; return win; } function gli_delete_window(win) { var prev, next; if (GiDispa) GiDispa.class_unregister('window', win); geometry_changed = true; win.echostr = null; if (win.str) { gli_delete_stream(win.str); win.str = null; } prev = win.prev; next = win.next; win.prev = null; win.next = null; if (prev) prev.next = next; else gli_windowlist = next; if (next) next.prev = prev; win.parent = null; win.rock = null; win.disprock = null; } function gli_windows_unechostream(str) { var win; for (win=gli_windowlist; win; win=win.next) { if (win.echostr === str) win.echostr = null; } } /* Add a (Javascript) string to the given window's display. */ function gli_window_put_string(win, val) { var ix, ch; //### might be efficient to split the implementation up into //### gli_window_buffer_put_string(), etc, since many functions //### know the window type when they call this switch (win.type) { case Const.wintype_TextBuffer: if (win.style != win.accumstyle || win.hyperlink != win.accumhyperlink) gli_window_buffer_deaccumulate(win); win.accum.push(val); break; case Const.wintype_TextGrid: for (ix=0; ix= win.gridwidth) { win.cursorx = 0; win.cursory++; } if (win.cursory < 0) win.cursory = 0; else if (win.cursory >= win.gridheight) break; /* outside the window */ if (ch == "\n") { /* a newline just moves the cursor. */ win.cursory++; win.cursorx = 0; continue; } lineobj = win.lines[win.cursory]; lineobj.dirty = true; lineobj.chars[win.cursorx] = ch; lineobj.styles[win.cursorx] = win.style; lineobj.hyperlinks[win.cursorx] = win.hyperlink; win.cursorx++; /* We can leave the cursor outside the window, since it will be canonicalized next time a character is printed. */ } break; } } /* Canonicalize the cursor position. That is, the cursor may have been left outside the window area; wrap it if necessary. Returns true if the cursor winds up wrapped outside the window entirely; false if the cursor winds up at a legal printing position. */ function gli_window_grid_canonicalize(win) { if (win.cursorx < 0) win.cursorx = 0; else if (win.cursorx >= win.gridwidth) { win.cursorx = 0; win.cursory++; } if (win.cursory < 0) win.cursory = 0; else if (win.cursory >= win.gridheight) return true; /* outside the window */ return false; } /* Take the accumulation of strings (since the last style change) and assemble them into a buffer window update. This must be called after each style change; it must also be called right before GlkOte.update(). (Actually we call it right before win.accum.push if the style has changed -- there's no need to call for *every* style change if no text is being pushed out in between.) */ function gli_window_buffer_deaccumulate(win) { var conta = win.content; var stylename = StyleNameMap[win.accumstyle]; var text, ls, ix, obj, arr; if (win.accum.length) { text = win.accum.join(''); ls = text.split('\n'); for (ix=0; ix win.gridheight) { win.lines.length = win.gridheight; } else if (oldheight < win.gridheight) { for (ix=oldheight; ix win.gridwidth) { lineobj.dirty = true; lineobj.chars.length = win.gridwidth; lineobj.styles.length = win.gridwidth; lineobj.hyperlinks.length = win.gridwidth; } else if (oldwidth < win.gridwidth) { lineobj.dirty = true; for (cx=oldwidth; cx= max) { split = min; } else { split = Math.min(Math.max(split, min), max-splitwid); } win.pair_splitpos = split; win.pair_splitwidth = splitwid; if (win.pair_vertical) { box1 = { left: win.bbox.left, right: win.pair_splitpos, top: win.bbox.top, bottom: win.bbox.bottom }; box2 = { left: box1.right + win.pair_splitwidth, right: win.bbox.right, top: win.bbox.top, bottom: win.bbox.bottom }; } else { box1 = { top: win.bbox.top, bottom: win.pair_splitpos, left: win.bbox.left, right: win.bbox.right }; box2 = { top: box1.bottom + win.pair_splitwidth, bottom: win.bbox.bottom, left: win.bbox.left, right: win.bbox.right }; } if (!win.pair_backward) { ch1 = win.child1; ch2 = win.child2; } else { ch1 = win.child2; ch2 = win.child1; } gli_window_rearrange(ch1, box1); gli_window_rearrange(ch2, box2); break; } } function gli_new_stream(type, readable, writable, rock) { var str = {}; str.type = type; str.rock = rock; str.disprock = undefined; str.unicode = false; /* isbinary is only meaningful for Resource and streaming-File streams */ str.isbinary = false; str.streaming = false; str.ref = null; str.win = null; str.file = null; /* for buffer mode */ str.buf = null; str.bufpos = 0; str.buflen = 0; str.bufeof = 0; str.timer_id = null; str.flush_func = null; /* for streaming mode */ str.fstream = null; str.readcount = 0; str.writecount = 0; str.readable = readable; str.writable = writable; str.prev = null; str.next = gli_streamlist; gli_streamlist = str; if (str.next) str.next.prev = str; if (GiDispa) GiDispa.class_register('stream', str); return str; } function gli_delete_stream(str) { var prev, next; if (str === gli_currentstr) { gli_currentstr = null; } gli_windows_unechostream(str); if (str.type == strtype_Memory) { if (GiDispa) GiDispa.unretain_array(str.buf); } else if (str.type == strtype_File) { if (str.fstream) { str.fstream.fclose(); str.fstream = null; } } if (GiDispa) GiDispa.class_unregister('stream', str); prev = str.prev; next = str.next; str.prev = null; str.next = null; if (prev) prev.next = next; else gli_streamlist = next; if (next) next.prev = prev; str.fstream = null; str.buf = null; str.readable = false; str.writable = false; str.ref = null; str.win = null; str.file = null; str.rock = null; str.disprock = null; } function gli_stream_open_window(win) { var str; str = gli_new_stream(strtype_Window, false, true, 0); str.unicode = true; str.win = win; return str; } /* This is called on every write to a file stream. If a file is being written intermittently (a transcript file, for example) we'd like to flush the output every few seconds, in case the user closes the browser without closing the file ("script off"). We do this by setting a ten-second timer (if there isn't one set already). The timer calls a flush method on the stream. (If autosave is on, we'll wind up flushing on most glk_select calls, which isn't quite as nicely paced. But it's a minor problem.) */ function gli_stream_dirty_file(str) { if (str.streaming) GlkOte.log('### gli_stream_dirty_file called for streaming file!'); if (str.timer_id === null) { if (str.flush_func === null) { /* Bodge together a closure to act as a stream method. */ str.flush_func = function() { gli_stream_flush_file(str); }; } str.timer_id = setTimeout(str.flush_func, 10000); } } /* Write out the contents of a file stream to the "disk file". Because localStorage doesn't support appending, we have to dump the entire buffer out. */ function gli_stream_flush_file(str) { if (str.streaming) GlkOte.log('### gli_stream_flush_file called for streaming file!'); if (!(str.timer_id === null)) { clearTimeout(str.timer_id); } str.timer_id = null; Dialog.file_write(str.ref, str.buf); } function gli_new_fileref(filename, usage, rock, ref) { var fref = {}; fref.filename = filename; fref.rock = rock; fref.disprock = undefined; fref.textmode = ((usage & Const.fileusage_TextMode) != 0); fref.filetype = (usage & Const.fileusage_TypeMask); fref.filetypename = FileTypeMap[fref.filetype]; if (!fref.filetypename) { fref.filetypename = 'xxx'; } if (!ref) { var gameid = ''; if (fref.filetype == Const.fileusage_SavedGame) gameid = VM.get_signature(); ref = Dialog.file_construct_ref(fref.filename, fref.filetypename, gameid); } fref.ref = ref; fref.prev = null; fref.next = gli_filereflist; gli_filereflist = fref; if (fref.next) fref.next.prev = fref; if (GiDispa) GiDispa.class_register('fileref', fref); return fref; } function gli_delete_fileref(fref) { var prev, next; if (GiDispa) GiDispa.class_unregister('fileref', fref); prev = fref.prev; next = fref.next; fref.prev = null; fref.next = null; if (prev) prev.next = next; else gli_filereflist = next; if (next) next.prev = prev; fref.filename = null; fref.ref = null; fref.rock = null; fref.disprock = null; } /* Write one character (given as a Unicode value) to a stream. This is called by both the one-byte and four-byte character APIs. */ function gli_put_char(str, ch) { if (!str || !str.writable) throw('gli_put_char: invalid stream'); if (!str.unicode) { if (ch < 0 || ch >= 0x100) ch = 63; // '?' } str.writecount += 1; switch (str.type) { case strtype_File: if (str.streaming) { if (!str.unicode) { str.buffer4[0] = ch; str.fstream.fwrite(str.buffer4, 1); } else { if (!str.isbinary) { /* cheap UTF-8 stream */ var len; if (ch < 0x10000) { len = str.buffer4.write(String.fromCharCode(ch)); str.fstream.fwrite(str.buffer4, len); // utf8 } else { /* String.fromCharCode chokes on astral characters; do it the hard way */ var arr8 = UniArrayToUTF8([ch]); var buf = str.fstream.BufferClass.from(arr8); str.fstream.fwrite(buf); } } else { /* cheap big-endian stream */ str.buffer4.writeUInt32BE(ch, 0, true); str.fstream.fwrite(str.buffer4, 4); } } } else { /* non-streaming... */ gli_stream_dirty_file(str); if (!str.unicode || (ch < 0x80 && !str.isbinary)) { if (str.bufpos < str.buflen) { str.buf[str.bufpos] = ch; str.bufpos += 1; if (str.bufpos > str.bufeof) str.bufeof = str.bufpos; } } else { var arr; if (!str.isbinary) arr = UniArrayToUTF8([ch]); else arr = UniArrayToBE32([ch]); var len = arr.length; if (len > str.buflen-str.bufpos) len = str.buflen-str.bufpos; for (ix=0; ix str.bufeof) str.bufeof = str.bufpos; } } break; case strtype_Memory: if (str.bufpos < str.buflen) { str.buf[str.bufpos] = ch; str.bufpos += 1; if (str.bufpos > str.bufeof) str.bufeof = str.bufpos; } break; case strtype_Window: if (str.win.line_request) throw('gli_put_char: window has pending line request'); gli_window_put_string(str.win, CharToString(ch)); if (str.win.echostr) gli_put_char(str.win.echostr, ch); break; } } /* Write characters (given as an array of Unicode values) to a stream. This is called by both the one-byte and four-byte character APIs. The "allbytes" argument is a hint that all the array values are already in the range 0-255. */ function gli_put_array(str, arr, allbytes) { var ix, len, val; if (!str || !str.writable) throw('gli_put_array: invalid stream'); if (!str.unicode && !allbytes) { arr = TrimArrayToBytes(arr); allbytes = true; } str.writecount += arr.length; switch (str.type) { case strtype_File: if (str.streaming) { if (!str.unicode) { var buf = str.fstream.BufferClass.from(arr); str.fstream.fwrite(buf); } else { if (!str.isbinary) { /* cheap UTF-8 stream */ var arr8 = UniArrayToUTF8(arr); var buf = str.fstream.BufferClass.from(arr8); str.fstream.fwrite(buf); } else { /* cheap big-endian stream */ var buf = str.fstream.BufferClass.alloc(4*arr.length); for (ix=0; ix str.buflen-str.bufpos) len = str.buflen-str.bufpos; for (ix=0; ix str.bufeof) str.bufeof = str.bufpos; } break; case strtype_Memory: len = arr.length; if (len > str.buflen-str.bufpos) len = str.buflen-str.bufpos; for (ix=0; ix str.bufeof) str.bufeof = str.bufpos; break; case strtype_Window: if (str.win.line_request) throw('gli_put_array: window has pending line request'); if (allbytes) val = String.fromCharCode.apply(this, arr); else val = UniArrayToString(arr); gli_window_put_string(str.win, val); if (str.win.echostr) gli_put_array(str.win.echostr, arr, allbytes); break; } } function gli_get_char(str, want_unicode) { var ch; if (!str || !str.readable) return -1; switch (str.type) { case strtype_File: if (str.streaming) { if (!str.unicode) { var len = str.fstream.fread(str.buffer4, 1); if (!len) return -1; str.readcount++; return str.buffer4[0]; } else { if (!str.isbinary) { /* slightly less cheap UTF8 stream */ var val0, val1, val2, val3; var len = str.fstream.fread(str.buffer4, 1); if (!len) return -1; val0 = str.buffer4[0]; if (val0 < 0x80) { ch = val0; } else { var len = str.fstream.fread(str.buffer4, 1); if (!len) return -1; val1 = str.buffer4[0]; if ((val1 & 0xC0) != 0x80) return -1; if ((val0 & 0xE0) == 0xC0) { ch = (val0 & 0x1F) << 6; ch |= (val1 & 0x3F); } else { var len = str.fstream.fread(str.buffer4, 1); if (!len) return -1; val2 = str.buffer4[0]; if ((val2 & 0xC0) != 0x80) return -1; if ((val0 & 0xF0) == 0xE0) { ch = (((val0 & 0xF)<<12) & 0x0000F000); ch |= (((val1 & 0x3F)<<6) & 0x00000FC0); ch |= (((val2 & 0x3F)) & 0x0000003F); } else if ((val0 & 0xF0) == 0xF0) { var len = str.fstream.fread(str.buffer4, 1); if (!len) return -1; val3 = str.buffer4[0]; if ((val3 & 0xC0) != 0x80) return -1; ch = (((val0 & 0x7)<<18) & 0x1C0000); ch |= (((val1 & 0x3F)<<12) & 0x03F000); ch |= (((val2 & 0x3F)<<6) & 0x000FC0); ch |= (((val3 & 0x3F)) & 0x00003F); } else { return -1; } } } } else { /* cheap big-endian stream */ var len = str.fstream.fread(str.buffer4, 4); if (len < 4) return -1; /*### or buf.readUInt32BE(0, true) */ ch = (str.buffer4[0] << 24); ch |= (str.buffer4[1] << 16); ch |= (str.buffer4[2] << 8); ch |= str.buffer4[3]; } str.readcount++; ch >>>= 0; if (!want_unicode && ch >= 0x100) return 63; // return '?' return ch; } } /* non-streaming, fall through to resource... */ case strtype_Resource: if (str.unicode) { if (str.isbinary) { /* cheap big-endian stream */ if (str.bufpos >= str.bufeof) return -1; ch = str.buf[str.bufpos]; str.bufpos++; if (str.bufpos >= str.bufeof) return -1; ch = (ch << 8) | (str.buf[str.bufpos] & 0xFF); str.bufpos++; if (str.bufpos >= str.bufeof) return -1; ch = (ch << 8) | (str.buf[str.bufpos] & 0xFF); str.bufpos++; if (str.bufpos >= str.bufeof) return -1; ch = (ch << 8) | (str.buf[str.bufpos] & 0xFF); str.bufpos++; } else { /* slightly less cheap UTF8 stream */ var val0, val1, val2, val3; if (str.bufpos >= str.bufeof) return -1; val0 = str.buf[str.bufpos]; str.bufpos++; if (val0 < 0x80) { ch = val0; } else { if (str.bufpos >= str.bufeof) return -1; val1 = str.buf[str.bufpos]; str.bufpos++; if ((val1 & 0xC0) != 0x80) return -1; if ((val0 & 0xE0) == 0xC0) { ch = (val0 & 0x1F) << 6; ch |= (val1 & 0x3F); } else { if (str.bufpos >= str.bufeof) return -1; val2 = str.buf[str.bufpos]; str.bufpos++; if ((val2 & 0xC0) != 0x80) return -1; if ((val0 & 0xF0) == 0xE0) { ch = (((val0 & 0xF)<<12) & 0x0000F000); ch |= (((val1 & 0x3F)<<6) & 0x00000FC0); ch |= (((val2 & 0x3F)) & 0x0000003F); } else if ((val0 & 0xF0) == 0xF0) { if (str.bufpos >= str.bufeof) return -1; val3 = str.buf[str.bufpos]; str.bufpos++; if ((val3 & 0xC0) != 0x80) return -1; ch = (((val0 & 0x7)<<18) & 0x1C0000); ch |= (((val1 & 0x3F)<<12) & 0x03F000); ch |= (((val2 & 0x3F)<<6) & 0x000FC0); ch |= (((val3 & 0x3F)) & 0x00003F); } else { return -1; } } } } str.readcount++; ch >>>= 0; if (!want_unicode && ch >= 0x100) return 63; // return '?' return ch; } /* non-unicode file/resource, fall through to memory... */ case strtype_Memory: if (str.bufpos < str.bufeof) { ch = str.buf[str.bufpos]; str.bufpos++; str.readcount++; if (!want_unicode && ch >= 0x100) return 63; // return '?' return ch; } else { return -1; // end of stream } default: return -1; } } function gli_get_line(str, buf, want_unicode) { if (!str || !str.readable) return 0; var len = buf.length; var gotnewline; switch (str.type) { case strtype_File: if (str.streaming) { if (len == 0) return 0; len -= 1; /* for the terminal null */ gotnewline = false; for (lx=0; lx= str.bufeof) { len = 0; } else { if (str.bufpos + len > str.bufeof) { len = str.bufeof - str.bufpos; } } gotnewline = false; if (!want_unicode) { for (lx=0; lx= 0x100) ch = 63; // ch = '?' buf[lx] = ch; gotnewline = (ch == 10); } } else { for (lx=0; lx= str.bufeof) { len = 0; } else { if (str.bufpos + len > str.bufeof) { len = str.bufeof - str.bufpos; } } if (!want_unicode) { for (lx=0; lx= 0x100) ch = 63; // ch = '?' buf[lx] = ch; } } else { for (lx=0; lx str.buflen-str.bufpos) len = str.buflen-str.bufpos; for (ix=0; ix str.bufeof) str.bufeof = str.bufpos; } break; case strtype_Memory: len = val.length; if (len > str.buflen-str.bufpos) len = str.buflen-str.bufpos; if (str.unicode || allbytes) { for (ix=0; ix= 0x100) ch = 63; // '?' str.buf[str.bufpos+ix] = ch; } } str.bufpos += len; if (str.bufpos > str.bufeof) str.bufeof = str.bufpos; break; case strtype_Window: if (str.win.line_request) throw('glk_put_jstring: window has pending line request'); gli_window_put_string(str.win, val); if (str.win.echostr) glk_put_jstring_stream(str.win.echostr, val, allbytes); break; } } function gli_set_style(str, val) { if (!str || !str.writable) throw('gli_set_style: invalid stream'); if (val >= Const.style_NUMSTYLES) val = 0; if (str.type == strtype_Window) { str.win.style = val; if (str.win.echostr) gli_set_style(str.win.echostr, val); } } function gli_set_hyperlink(str, val) { if (!str || !str.writable) throw('gli_set_hyperlink: invalid stream'); if (str.type == strtype_Window) { str.win.hyperlink = val; if (str.win.echostr) gli_set_hyperlink(str.win.echostr, val); } } /* The catalog of Glk API functions. */ function glk_exit() { /* For safety, this is fast and idempotent. */ has_exited = true; ui_disabled = true; gli_selectref = null; if (option_exit_warning) GlkOte.warning(option_exit_warning); update('exit'); return DidNotReturn; } function glk_tick() { /* Do nothing. */ } function glk_gestalt(sel, val) { return glk_gestalt_ext(sel, val, null); } function glk_gestalt_ext(sel, val, arr) { switch (sel) { case 0: // gestalt_Version /* This implements Glk spec version 0.7.4. */ return 0x00000704; case 1: // gestalt_CharInput /* This is not a terrific approximation. Return false for function keys, control keys, and the high-bit non-printables. For everything else in the Unicode range, return true. */ if (val <= Const.keycode_Left && val >= Const.keycode_End) return 1; if (val >= 0x100000000-Const.keycode_MAXVAL) return 0; if (val > 0x10FFFF) return 0; if ((val >= 0 && val < 32) || (val >= 127 && val < 160)) return 0; return 1; case 2: // gestalt_LineInput /* Same as the above, except no special keys. */ if (val > 0x10FFFF) return 0; if ((val >= 0 && val < 32) || (val >= 127 && val < 160)) return 0; return 1; case 3: // gestalt_CharOutput /* Same thing again. We assume that all printable characters, as well as the placeholders for nonprintables, are one character wide. */ if ((val > 0x10FFFF) || (val >= 0 && val < 32) || (val >= 127 && val < 160)) { if (arr) arr[0] = 1; return 0; // gestalt_CharOutput_CannotPrint } if (arr) arr[0] = 1; return 2; // gestalt_CharOutput_ExactPrint case 4: // gestalt_MouseInput if (val == Const.wintype_TextGrid) return 1; if (support.graphics && val == Const.wintype_Graphics) return 1; return 0; case 5: // gestalt_Timer return support.timer || 0; case 6: // gestalt_Graphics return support.graphics || 0; case 7: // gestalt_DrawImage if (support.graphics && (val == Const.wintype_TextBuffer || val == Const.wintype_Graphics)) return 1; return 0; case 8: // gestalt_Sound return 0; case 9: // gestalt_SoundVolume return 0; case 10: // gestalt_SoundNotify return 0; case 11: // gestalt_Hyperlinks return support.hyperlinks || 0; case 12: // gestalt_HyperlinkInput if (support.hyperlinks && (val == Const.wintype_TextBuffer || val == Const.wintype_TextGrid)) return 1; else return 0; case 13: // gestalt_SoundMusic return 0; case 14: // gestalt_GraphicsTransparency return support.graphics || 0; case 15: // gestalt_Unicode return 1; case 16: // gestalt_UnicodeNorm return 1; case 17: // gestalt_LineInputEcho return 1; case 18: // gestalt_LineTerminators return 1; case 19: // gestalt_LineTerminatorKey /* Really this result should be inspected from glkote.js. Since it isn't, be sure to keep these values in sync with terminator_key_names. */ if (val == Const.keycode_Escape) return 1; if (val >= Const.keycode_Func12 && val <= Const.keycode_Func1) return 1; return 0; case 20: // gestalt_DateTime return 1; case 21: // gestalt_Sound2 return 0; case 22: // gestalt_ResourceStream return 1; case 23: // gestalt_GraphicsCharInput return 0; } if (option_glk_gestalt_hook) { var res = option_glk_gestalt_hook(sel, val, arr); if (res !== undefined) return res; } return 0; } function glk_window_iterate(win, rockref) { if (!win) win = gli_windowlist; else win = win.next; if (win) { if (rockref) rockref.set_value(win.rock); return win; } if (rockref) rockref.set_value(0); return null; } function glk_window_get_rock(win) { if (!win) throw('glk_window_get_rock: invalid window'); return win.rock; } function glk_window_get_root() { return gli_rootwin; } function glk_window_open(splitwin, method, size, wintype, rock) { var oldparent, box, val; var pairwin, newwin; if (!gli_rootwin) { if (splitwin) throw('glk_window_open: splitwin must be null for first window'); oldparent = null; box = { left: content_metrics.outspacingx, top: content_metrics.outspacingy, right: content_metrics.width-content_metrics.outspacingx, bottom: content_metrics.height-content_metrics.outspacingy }; } else { if (!splitwin) throw('glk_window_open: splitwin must not be null'); val = (method & Const.winmethod_DivisionMask); if (val != Const.winmethod_Fixed && val != Const.winmethod_Proportional) throw('glk_window_open: invalid method (not fixed or proportional)'); val = (method & Const.winmethod_DirMask); if (val != Const.winmethod_Above && val != Const.winmethod_Below && val != Const.winmethod_Left && val != Const.winmethod_Right) throw('glk_window_open: invalid method (bad direction)'); box = splitwin.bbox; oldparent = splitwin.parent; if (oldparent && oldparent.type != Const.wintype_Pair) throw('glk_window_open: parent window is not Pair'); } newwin = gli_new_window(wintype, rock); switch (newwin.type) { case Const.wintype_TextBuffer: /* accum is a list of strings of a given style; newly-printed text is pushed onto the list. accumstyle is the style of that text. Anything printed in a different style (or hyperlink value) triggers a call to gli_window_buffer_deaccumulate, which cleans out accum and adds the results to the content array. The content is in GlkOte format. */ newwin.accum = []; newwin.accumstyle = null; newwin.accumhyperlink = 0; newwin.content = []; newwin.clearcontent = false; newwin.reserve = []; /* autosave of recent content */ break; case Const.wintype_TextGrid: /* lines is a list of line objects. A line looks like { chars: [...], styles: [...], hyperlinks: [...], dirty: bool }. */ newwin.gridwidth = 0; newwin.gridheight = 0; newwin.lines = []; newwin.cursorx = 0; newwin.cursory = 0; break; case Const.wintype_Graphics: if (!support.graphics) { /* Graphics windows not supported; silently return null */ gli_delete_window(newwin); return null; } newwin.content = []; newwin.reserve = []; /* autosave of recent content */ break; case Const.wintype_Blank: break; case Const.wintype_Pair: throw('glk_window_open: cannot open pair window directly') default: /* Silently return null */ gli_delete_window(newwin); return null; } if (!splitwin) { gli_rootwin = newwin; gli_window_rearrange(newwin, box); } else { /* create pairwin, with newwin as the key */ pairwin = gli_new_window(Const.wintype_Pair, 0); pairwin.pair_dir = method & Const.winmethod_DirMask; pairwin.pair_division = method & Const.winmethod_DivisionMask; pairwin.pair_key = newwin; pairwin.pair_keydamage = false; pairwin.pair_size = size; pairwin.pair_hasborder = ((method & Const.winmethod_BorderMask) == Const.winmethod_Border); pairwin.pair_vertical = (pairwin.pair_dir == Const.winmethod_Left || pairwin.pair_dir == Const.winmethod_Right); pairwin.pair_backward = (pairwin.pair_dir == Const.winmethod_Left || pairwin.pair_dir == Const.winmethod_Above); pairwin.child1 = splitwin; pairwin.child2 = newwin; splitwin.parent = pairwin; newwin.parent = pairwin; pairwin.parent = oldparent; if (oldparent) { if (oldparent.child1 == splitwin) oldparent.child1 = pairwin; else oldparent.child2 = pairwin; } else { gli_rootwin = pairwin; } gli_window_rearrange(pairwin, box); } return newwin; } function glk_window_close(win, statsref) { if (!win) throw('glk_window_close: invalid window'); if (win === gli_rootwin || !win.parent) { /* close the root window, which means all windows. */ gli_rootwin = null; /* begin (simpler) closation */ gli_stream_fill_result(win.str, statsref); gli_window_close(win, true); } else { /* have to jigger parent */ var pairwin, grandparwin, sibwin, box, wx, keydamage_flag; pairwin = win.parent; if (win === pairwin.child1) sibwin = pairwin.child2; else if (win === pairwin.child2) sibwin = pairwin.child1; else throw('glk_window_close: window tree is corrupted'); box = pairwin.bbox; grandparwin = pairwin.parent; if (!grandparwin) { gli_rootwin = sibwin; sibwin.parent = null; } else { if (grandparwin.child1 === pairwin) grandparwin.child1 = sibwin; else grandparwin.child2 = sibwin; sibwin.parent = grandparwin; } /* Begin closation */ gli_stream_fill_result(win.str, statsref); /* Close the child window (and descendants), so that key-deletion can crawl up the tree to the root window. */ gli_window_close(win, true); /* This probably isn't necessary, but the child *is* gone, so just in case. */ if (win === pairwin.child1) { pairwin.child1 = null; } else if (win === pairwin.child2) { pairwin.child2 = null; } /* Now we can delete the parent pair. */ gli_window_close(pairwin, false); keydamage_flag = false; for (wx=sibwin; wx; wx=wx.parent) { if (wx.type == Const.wintype_Pair) { if (wx.pair_keydamage) { keydamage_flag = true; wx.pair_keydamage = false; } } } if (keydamage_flag) { box = content_box; gli_window_rearrange(gli_rootwin, box); } else { gli_window_rearrange(sibwin, box); } } } function glk_window_get_size(win, widthref, heightref) { if (!win) throw('glk_window_get_size: invalid window'); var wid = 0; var hgt = 0; var boxwidth, boxheight; switch (win.type) { case Const.wintype_TextGrid: boxwidth = win.bbox.right - win.bbox.left; boxheight = win.bbox.bottom - win.bbox.top; wid = Math.max(0, Math.floor((boxwidth-content_metrics.gridmarginx) / content_metrics.gridcharwidth)); hgt = Math.max(0, Math.floor((boxheight-content_metrics.gridmarginy) / content_metrics.gridcharheight)); break; case Const.wintype_TextBuffer: boxwidth = win.bbox.right - win.bbox.left; boxheight = win.bbox.bottom - win.bbox.top; wid = Math.max(0, Math.floor((boxwidth-content_metrics.buffermarginx) / content_metrics.buffercharwidth)); hgt = Math.max(0, Math.floor((boxheight-content_metrics.buffermarginy) / content_metrics.buffercharheight)); break; case Const.wintype_Graphics: boxwidth = win.bbox.right - win.bbox.left; boxheight = win.bbox.bottom - win.bbox.top; wid = boxwidth - content_metrics.graphicsmarginx; hgt = boxheight - content_metrics.graphicsmarginy; break; } if (widthref) widthref.set_value(wid); if (heightref) heightref.set_value(hgt); } function glk_window_set_arrangement(win, method, size, keywin) { var wx, newdir, newvertical, newbackward; if (!win) throw('glk_window_set_arrangement: invalid window'); if (win.type != Const.wintype_Pair) throw('glk_window_set_arrangement: not a pair window'); if (keywin) { if (keywin.type == Const.wintype_Pair) throw('glk_window_set_arrangement: keywin cannot be a pair window'); for (wx=keywin; wx; wx=wx.parent) { if (wx == win) break; } if (!wx) throw('glk_window_set_arrangement: keywin must be a descendant'); } newdir = method & Const.winmethod_DirMask; newvertical = (newdir == Const.winmethod_Left || newdir == Const.winmethod_Right); newbackward = (newdir == Const.winmethod_Left || newdir == Const.winmethod_Above); if (!keywin) keywin = win.pair_key; if (newvertical && !win.pair_vertical) throw('glk_window_set_arrangement: split must stay horizontal'); if (!newvertical && win.pair_vertical) throw('glk_window_set_arrangement: split must stay vertical'); if (keywin && keywin.type == Const.wintype_Blank && (method & Const.winmethod_DivisionMask) == Const.winmethod_Fixed) throw('glk_window_set_arrangement: a blank window cannot have a fixed size'); if ((newbackward && !win.pair_backward) || (!newbackward && win.pair_backward)) { /* switch the children */ wx = win.child1; win.child1 = win.child2; win.child2 = wx; } /* set up everything else */ win.pair_dir = newdir; win.pair_division = (method & Const.winmethod_DivisionMask); win.pair_key = keywin; win.pair_size = size; win.pair_hasborder = ((method & Const.winmethod_BorderMask) == Const.winmethod_Border); win.pair_vertical = (win.pair_dir == Const.winmethod_Left || win.pair_dir == Const.winmethod_Right); win.pair_backward = (win.pair_dir == Const.winmethod_Left || win.pair_dir == Const.winmethod_Above); gli_window_rearrange(win, win.bbox); } function glk_window_get_arrangement(win, methodref, sizeref, keywinref) { if (!win) throw('glk_window_get_arrangement: invalid window'); if (win.type != Const.wintype_Pair) throw('glk_window_get_arrangement: not a pair window'); if (sizeref) sizeref.set_value(win.pair_size); if (keywinref) keywinref.set_value(win.pair_key); if (methodref) methodref.set_value(win.pair_dir | win.pair_division | (win.pair_hasborder ? Const.winmethod_Border : Const.winmethod_NoBorder)); } function glk_window_get_type(win) { if (!win) throw('glk_window_get_type: invalid window'); return win.type; } function glk_window_get_parent(win) { if (!win) throw('glk_window_get_parent: invalid window'); return win.parent; } function glk_window_clear(win) { var ix, cx, lineobj; if (!win) throw('glk_window_clear: invalid window'); if (win.line_request) { throw('glk_window_clear: window has pending line request'); } switch (win.type) { case Const.wintype_TextBuffer: win.accum.length = 0; win.accumstyle = null; win.accumhyperlink = 0; win.content.length = 0; win.clearcontent = true; break; case Const.wintype_TextGrid: win.cursorx = 0; win.cursory = 0; for (ix=0; ix str.bufeof) pos = str.bufeof; str.bufpos = pos; } } function glk_stream_get_position(str) { if (!str) throw('glk_stream_get_position: invalid stream'); switch (str.type) { case strtype_File: if (str.streaming) { return str.fstream.ftell(); } /* fall through to memory... */ case strtype_Resource: /* fall through to memory... */ case strtype_Memory: return str.bufpos; default: return 0; } } function glk_stream_set_current(str) { gli_currentstr = str; } function glk_stream_get_current() { return gli_currentstr; } function glk_fileref_create_temp(usage, rock) { var filetype = (usage & Const.fileusage_TypeMask); var filetypename = FileTypeMap[filetype]; var ref = Dialog.file_construct_temp_ref(filetypename); fref = gli_new_fileref(ref.filename, usage, rock, ref); return fref; } function glk_fileref_create_by_name(usage, filename, rock) { /* Filenames that do not come from the user must be cleaned up. */ filename = Dialog.file_clean_fixed_name(filename, (usage & Const.fileusage_TypeMask)); fref = gli_new_fileref(filename, usage, rock, null); return fref; } function glk_fileref_create_by_prompt(usage, fmode, rock) { var modename; var filetype = (usage & Const.fileusage_TypeMask); var filetypename = FileTypeMap[filetype]; if (!filetypename) { filetypename = 'xxx'; } switch (fmode) { case Const.filemode_Write: modename = 'write'; break; case Const.filemode_ReadWrite: modename = 'readwrite'; break; case Const.filemode_WriteAppend: modename = 'writeappend'; break; case Const.filemode_Read: default: modename = 'read'; break; } var special = { type: 'fileref_prompt', filetype: filetypename, filemode: modename }; var callback = { usage: usage, rock: rock }; if (filetype == Const.fileusage_SavedGame) special.gameid = VM.get_signature(); ui_specialinput = special; ui_specialcallback = callback; gli_selectref = null; return DidNotReturn; } function gli_fileref_create_by_prompt_callback(obj) { var ref = obj.value; var usage = ui_specialcallback.usage; var rock = ui_specialcallback.rock; var fref = null; if (ref) { fref = gli_new_fileref(ref.filename, usage, rock, ref); } // If reading a file which doesn't exist, return null if ( ui_specialinput.filemode === 'read' && !Dialog.file_ref_exists( fref.ref ) ) { glk_fileref_destroy( fref ); fref = null; } ui_specialinput = null; ui_specialcallback = null; if (GiDispa) GiDispa.prepare_resume(fref); VM.resume(fref); } function glk_fileref_destroy(fref) { if (!fref) throw('glk_fileref_destroy: invalid fileref'); gli_delete_fileref(fref); } function glk_fileref_iterate(fref, rockref) { if (!fref) fref = gli_filereflist; else fref = fref.next; if (fref) { if (rockref) rockref.set_value(fref.rock); return fref; } if (rockref) rockref.set_value(0); return null; } function glk_fileref_get_rock(fref) { if (!fref) throw('glk_fileref_get_rock: invalid fileref'); return fref.rock; } function glk_fileref_delete_file(fref) { if (!fref) throw('glk_fileref_delete_file: invalid fileref'); Dialog.file_remove_ref(fref.ref); } function glk_fileref_does_file_exist(fref) { if (!fref) throw('glk_fileref_does_file_exist: invalid fileref'); if (Dialog.file_ref_exists(fref.ref)) return 1; else return 0; } function glk_fileref_create_from_fileref(usage, oldfref, rock) { if (!oldfref) throw('glk_fileref_create_from_fileref: invalid fileref'); var fref = gli_new_fileref(oldfref.filename, usage, rock, null); return fref; } function glk_put_char(ch) { gli_put_char(gli_currentstr, ch & 0xFF); } function glk_put_char_stream(str, ch) { gli_put_char(str, ch & 0xFF); } function glk_put_string(val) { glk_put_jstring_stream(gli_currentstr, val, true); } function glk_put_string_stream(str, val) { glk_put_jstring_stream(str, val, true); } function glk_put_buffer(arr) { arr = TrimArrayToBytes(arr); gli_put_array(gli_currentstr, arr, true); } function glk_put_buffer_stream(str, arr) { arr = TrimArrayToBytes(arr); gli_put_array(str, arr, true); } function glk_set_style(val) { gli_set_style(gli_currentstr, val); } function glk_set_style_stream(str, val) { gli_set_style(str, val); } function glk_get_char_stream(str) { if (!str) throw('glk_get_char_stream: invalid stream'); return gli_get_char(str, false); } function glk_get_line_stream(str, buf) { if (!str) throw('glk_get_line_stream: invalid stream'); return gli_get_line(str, buf, false); } function glk_get_buffer_stream(str, buf) { if (!str) throw('glk_get_buffer_stream: invalid stream'); return gli_get_buffer(str, buf, false); } function glk_char_to_lower(val) { if (val >= 0x41 && val <= 0x5A) return val + 0x20; if (val >= 0xC0 && val <= 0xDE && val != 0xD7) return val + 0x20; return val; } function glk_char_to_upper(val) { if (val >= 0x61 && val <= 0x7A) return val - 0x20; if (val >= 0xE0 && val <= 0xFE && val != 0xF7) return val - 0x20; return val; } /* Style hints are not supported. We will use the new style system. */ function glk_stylehint_set(wintype, styl, hint, value) { } function glk_stylehint_clear(wintype, styl, hint) { } function glk_style_distinguish(win, styl1, styl2) { return 0; } function glk_style_measure(win, styl, hint, resultref) { if (resultref) resultref.set_value(0); return 0; } function glk_select(eventref) { gli_selectref = eventref; return DidNotReturn; } function glk_select_poll(eventref) { /* Because the Javascript interpreter is single-threaded, we cannot have gotten a timer event since the last glk_select call. */ eventref.set_field(0, Const.evtype_None); eventref.set_field(1, null); eventref.set_field(2, 0); eventref.set_field(3, 0); if (gli_timer_interval) { var now = Date.now(); if (now - gli_timer_started > gli_timer_interval) { /* We're past the timer interval, even though we got no event. Let's pretend we did, reset it, and return a timer event. */ gli_timer_started = Date.now(); /* Resend timer request at next update. */ gli_timer_lastsent = null; eventref.set_field(0, Const.evtype_Timer); } } } function glk_request_line_event(win, buf, initlen) { if (!win) throw('glk_request_line_event: invalid window'); if (win.char_request || win.line_request) throw('glk_request_line_event: window already has keyboard request'); if (win.type == Const.wintype_TextBuffer || win.type == Const.wintype_TextGrid) { if (initlen) { /* This will be copied into the next update. */ var ls = buf.slice(0, initlen); if (!current_partial_outputs) current_partial_outputs = {}; current_partial_outputs[win.disprock] = ByteArrayToString(ls); } win.line_request = true; win.line_request_uni = false; if (win.type == Const.wintype_TextBuffer) win.request_echo_line_input = win.echo_line_input; else win.request_echo_line_input = true; win.input_generation = event_generation; win.linebuf = buf; if (GiDispa) GiDispa.retain_array(buf); } else { throw('glk_request_line_event: window does not support keyboard input'); } } function glk_cancel_line_event(win, eventref) { if (!win) throw('glk_cancel_line_event: invalid window'); if (!win.line_request) { if (eventref) { eventref.set_field(0, Const.evtype_None); eventref.set_field(1, null); eventref.set_field(2, 0); eventref.set_field(3, 0); } return; } var input = ""; var ix, val; if (current_partial_inputs) { val = current_partial_inputs[win.disprock]; if (val) input = val; } if (input.length > win.linebuf.length) input = input.slice(0, win.linebuf.length); if (win.request_echo_line_input) { ix = win.style; gli_set_style(win.str, Const.style_Input); gli_window_put_string(win, input); if (win.echostr) glk_put_jstring_stream(win.echostr, input); gli_set_style(win.str, ix); gli_window_put_string(win, "\n"); if (win.echostr) glk_put_jstring_stream(win.echostr, "\n"); } for (ix=0; ix= pos) break; grpstart = ix; while (ix < pos && unicode_combin_table[arr[ix]]) ix++; grpend = ix; if (grpend - grpstart >= 2) { /* Sort this group. */ for (jx = grpend-1; jx > grpstart; jx--) { for (kx = grpstart; kx < jx; kx++) { if (unicode_combin_table[arr[kx]] > unicode_combin_table[arr[kx+1]]) { tmp = arr[kx]; arr[kx] = arr[kx+1]; arr[kx+1] = tmp; } } } } } return pos; } function gli_buffer_canon_compose_uni(arr, numchars) { /* The algorithm for canonically composing characters in a string: for each base character, compare it to all the following combining characters (up to the next base character). If they're composable, compose them. Repeat until no more pairs are found. */ var ix, jx, curch, newch, curclass, newclass, map, pos; if (numchars == 0) return 0; pos = 0; curch = arr[0]; curclass = unicode_combin_table[curch]; if (curclass) curclass = 999; // just in case the first character is a combiner ix = 1; jx = ix; while (true) { if (jx >= numchars) { arr[pos] = curch; pos = ix; break; } newch = arr[jx]; newclass = unicode_combin_table[newch]; map = unicode_compo_table[curch]; if (map !== undefined && map[newch] !== undefined && (!curclass || (newclass && curclass < newclass))) { curch = map[newch]; arr[pos] = curch; } else { if (!newclass) { pos = ix; curch = newch; } curclass = newclass; arr[ix] = newch; ix++; } jx++; } return pos; } function glk_buffer_canon_decompose_uni(arr, numchars) { var arrlen = arr.length; var len; len = gli_buffer_canon_decompose_uni(arr, numchars); /* in case we stretched the array */ arr.length = arrlen; return len; } function glk_buffer_canon_normalize_uni(arr, numchars) { var arrlen = arr.length; var len; len = gli_buffer_canon_decompose_uni(arr, numchars); len = gli_buffer_canon_compose_uni(arr, len); /* in case we stretched the array */ arr.length = arrlen; return len; } function glk_put_char_uni(ch) { gli_put_char(gli_currentstr, ch); } function glk_put_string_uni(val) { glk_put_jstring_stream(gli_currentstr, val, false); } function glk_put_buffer_uni(arr) { gli_put_array(gli_currentstr, arr, false); } function glk_put_char_stream_uni(str, ch) { gli_put_char(str, ch); } function glk_put_string_stream_uni(str, val) { glk_put_jstring_stream(str, val, false); } function glk_put_buffer_stream_uni(str, arr) { gli_put_array(str, arr, false); } function glk_get_char_stream_uni(str) { if (!str) throw('glk_get_char_stream_uni: invalid stream'); return gli_get_char(str, true); } function glk_get_buffer_stream_uni(str, buf) { if (!str) throw('glk_get_buffer_stream_uni: invalid stream'); return gli_get_buffer(str, buf, true); } function glk_get_line_stream_uni(str, buf) { if (!str) throw('glk_get_line_stream_uni: invalid stream'); return gli_get_line(str, buf, true); } function glk_stream_open_file_uni(fref, fmode, rock) { if (!fref) throw('glk_stream_open_file_uni: invalid fileref'); var str; var fstream; if (fmode != Const.filemode_Read && fmode != Const.filemode_Write && fmode != Const.filemode_ReadWrite && fmode != Const.filemode_WriteAppend) throw('glk_stream_open_file_uni: illegal filemode'); if (fmode == Const.filemode_Read && !Dialog.file_ref_exists(fref.ref)) return null; if (!Dialog.streaming) { var content = null; if (fmode != Const.filemode_Write) { content = Dialog.file_read(fref.ref); } if (content == null) { content = []; if (fmode != Const.filemode_Read) { /* We just created this file. (Or perhaps we're in Write mode and we're truncating.) Write immediately, to create it and get the creation date right. */ Dialog.file_write(fref.ref, '', true); } } if (content.length == null) throw('glk_stream_open_file_uni: data read had no length'); } else { fstream = Dialog.file_fopen(fmode, fref.ref); if (!fstream) return null; } str = gli_new_stream(strtype_File, (fmode != Const.filemode_Write), (fmode != Const.filemode_Read), rock); str.unicode = true; str.isbinary = !fref.textmode; str.ref = fref.ref; str.origfmode = fmode; if (!Dialog.streaming) { str.streaming = false; str.buf = content; str.buflen = 0xFFFFFFFF; /* enormous */ if (fmode == Const.filemode_Write) str.bufeof = 0; else str.bufeof = content.length; if (fmode == Const.filemode_WriteAppend) str.bufpos = str.bufeof; else str.bufpos = 0; } else { str.streaming = true; str.fstream = fstream; /* We'll want a Buffer object around for short and writes. */ str.buffer4 = fstream.BufferClass.alloc(4); } return str; } function glk_stream_open_memory_uni(buf, fmode, rock) { var str; if (fmode != Const.filemode_Read && fmode != Const.filemode_Write && fmode != Const.filemode_ReadWrite) throw('glk_stream_open_memory: illegal filemode'); str = gli_new_stream(strtype_Memory, (fmode != Const.filemode_Write), (fmode != Const.filemode_Read), rock); str.unicode = true; if (buf) { str.buf = buf; str.buflen = buf.length; str.bufpos = 0; if (fmode == Const.filemode_Write) str.bufeof = 0; else str.bufeof = str.buflen; if (GiDispa) GiDispa.retain_array(buf); } return str; } function glk_request_char_event_uni(win) { if (!win) throw('glk_request_char_event: invalid window'); if (win.char_request || win.line_request) throw('glk_request_char_event: window already has keyboard request'); if (win.type == Const.wintype_TextBuffer || win.type == Const.wintype_TextGrid) { win.char_request = true; win.char_request_uni = true; win.input_generation = event_generation; } else { /* ### wintype_Graphics could accept char input if we set up the focus to allow it. See gestalt_GraphicsCharInput. */ throw('glk_request_char_event: window does not support keyboard input'); } } function glk_request_line_event_uni(win, buf, initlen) { if (!win) throw('glk_request_line_event: invalid window'); if (win.char_request || win.line_request) throw('glk_request_line_event: window already has keyboard request'); if (win.type == Const.wintype_TextBuffer || win.type == Const.wintype_TextGrid) { if (initlen) { /* This will be copied into the next update. */ var ls = buf.slice(0, initlen); if (!current_partial_outputs) current_partial_outputs = {}; current_partial_outputs[win.disprock] = UniArrayToString(ls); } win.line_request = true; win.line_request_uni = true; if (win.type == Const.wintype_TextBuffer) win.request_echo_line_input = win.echo_line_input; else win.request_echo_line_input = true; win.input_generation = event_generation; win.linebuf = buf; if (GiDispa) GiDispa.retain_array(buf); } else { throw('glk_request_line_event: window does not support keyboard input'); } } function glk_current_time(timevalref) { var now = new Date().getTime(); var usec; timevalref.set_field(0, Math.floor(now / 4294967296000)); timevalref.set_field(1, Math.floor(now / 1000) >>>0); usec = Math.floor((now % 1000) * 1000); if (usec < 0) usec = 1000000 + usec; timevalref.set_field(2, usec); } function glk_current_simple_time(factor) { var now = new Date().getTime(); return Math.floor(now / (factor * 1000)); } function glk_time_to_date_utc(timevalref, dateref) { var now = timevalref.get_field(0) * 4294967296000 + timevalref.get_field(1) * 1000 + timevalref.get_field(2) / 1000; var obj = new Date(now); dateref.set_field(0, obj.getUTCFullYear()) dateref.set_field(1, 1+obj.getUTCMonth()) dateref.set_field(2, obj.getUTCDate()) dateref.set_field(3, obj.getUTCDay()) dateref.set_field(4, obj.getUTCHours()) dateref.set_field(5, obj.getUTCMinutes()) dateref.set_field(6, obj.getUTCSeconds()) dateref.set_field(7, 1000*obj.getUTCMilliseconds()) } function glk_time_to_date_local(timevalref, dateref) { var now = timevalref.get_field(0) * 4294967296000 + timevalref.get_field(1) * 1000 + timevalref.get_field(2) / 1000; var obj = new Date(now); dateref.set_field(0, obj.getFullYear()) dateref.set_field(1, 1+obj.getMonth()) dateref.set_field(2, obj.getDate()) dateref.set_field(3, obj.getDay()) dateref.set_field(4, obj.getHours()) dateref.set_field(5, obj.getMinutes()) dateref.set_field(6, obj.getSeconds()) dateref.set_field(7, 1000*obj.getMilliseconds()) } function glk_simple_time_to_date_utc(time, factor, dateref) { var now = time*(1000*factor); var obj = new Date(now); dateref.set_field(0, obj.getUTCFullYear()) dateref.set_field(1, 1+obj.getUTCMonth()) dateref.set_field(2, obj.getUTCDate()) dateref.set_field(3, obj.getUTCDay()) dateref.set_field(4, obj.getUTCHours()) dateref.set_field(5, obj.getUTCMinutes()) dateref.set_field(6, obj.getUTCSeconds()) dateref.set_field(7, 1000*obj.getUTCMilliseconds()) } function glk_simple_time_to_date_local(time, factor, dateref) { var now = time*(1000*factor); var obj = new Date(now); dateref.set_field(0, obj.getFullYear()) dateref.set_field(1, 1+obj.getMonth()) dateref.set_field(2, obj.getDate()) dateref.set_field(3, obj.getDay()) dateref.set_field(4, obj.getHours()) dateref.set_field(5, obj.getMinutes()) dateref.set_field(6, obj.getSeconds()) dateref.set_field(7, 1000*obj.getMilliseconds()) } function glk_date_to_time_utc(dateref, timevalref) { var obj = new Date(0); obj.setUTCFullYear(dateref.get_field(0)); obj.setUTCMonth(dateref.get_field(1)-1); obj.setUTCDate(dateref.get_field(2)); obj.setUTCHours(dateref.get_field(4)); obj.setUTCMinutes(dateref.get_field(5)); obj.setUTCSeconds(dateref.get_field(6)); obj.setUTCMilliseconds(dateref.get_field(7)/1000); var now = obj.getTime(); var usec; timevalref.set_field(0, Math.floor(now / 4294967296000)); timevalref.set_field(1, Math.floor(now / 1000) >>>0); usec = Math.floor((now % 1000) * 1000); if (usec < 0) usec = 1000000 + usec; timevalref.set_field(2, usec); } function glk_date_to_time_local(dateref, timevalref) { var obj = new Date( dateref.get_field(0), dateref.get_field(1)-1, dateref.get_field(2), dateref.get_field(4), dateref.get_field(5), dateref.get_field(6), dateref.get_field(7)/1000); var now = obj.getTime(); var usec; timevalref.set_field(0, Math.floor(now / 4294967296000)); timevalref.set_field(1, Math.floor(now / 1000) >>>0); usec = Math.floor((now % 1000) * 1000); if (usec < 0) usec = 1000000 + usec; timevalref.set_field(2, usec); } function glk_date_to_simple_time_utc(dateref, factor) { var obj = new Date(0); obj.setUTCFullYear(dateref.get_field(0)); obj.setUTCMonth(dateref.get_field(1)-1); obj.setUTCDate(dateref.get_field(2)); obj.setUTCHours(dateref.get_field(4)); obj.setUTCMinutes(dateref.get_field(5)); obj.setUTCSeconds(dateref.get_field(6)); obj.setUTCMilliseconds(dateref.get_field(7)/1000); var now = obj.getTime(); return Math.floor(now / (factor * 1000)); } function glk_date_to_simple_time_local(dateref, factor) { var obj = new Date( dateref.get_field(0), dateref.get_field(1)-1, dateref.get_field(2), dateref.get_field(4), dateref.get_field(5), dateref.get_field(6), dateref.get_field(7)/1000); var now = obj.getTime(); return Math.floor(now / (factor * 1000)); } /* End of Glk namespace function. Return the object which will become the Glk global. */ var api = { version: '2.2.3', /* GlkOte/GlkApi version */ set_references: set_references, init : init, update : update, save_allstate : save_allstate, restore_allstate : restore_allstate, fatal_error : fatal_error, byte_array_to_string : ByteArrayToString, uni_array_to_string : UniArrayToString, Const : Const, RefBox : RefBox, RefStruct : RefStruct, DidNotReturn : DidNotReturn, call_may_not_return : call_may_not_return, glk_put_jstring : glk_put_jstring, glk_put_jstring_stream : glk_put_jstring_stream, glk_exit : glk_exit, glk_tick : glk_tick, glk_gestalt : glk_gestalt, glk_gestalt_ext : glk_gestalt_ext, glk_window_iterate : glk_window_iterate, glk_window_get_rock : glk_window_get_rock, glk_window_get_root : glk_window_get_root, glk_window_open : glk_window_open, glk_window_close : glk_window_close, glk_window_get_size : glk_window_get_size, glk_window_set_arrangement : glk_window_set_arrangement, glk_window_get_arrangement : glk_window_get_arrangement, glk_window_get_type : glk_window_get_type, glk_window_get_parent : glk_window_get_parent, glk_window_clear : glk_window_clear, glk_window_move_cursor : glk_window_move_cursor, glk_window_get_stream : glk_window_get_stream, glk_window_set_echo_stream : glk_window_set_echo_stream, glk_window_get_echo_stream : glk_window_get_echo_stream, glk_set_window : glk_set_window, glk_window_get_sibling : glk_window_get_sibling, glk_stream_iterate : glk_stream_iterate, glk_stream_get_rock : glk_stream_get_rock, glk_stream_open_file : glk_stream_open_file, glk_stream_open_memory : glk_stream_open_memory, glk_stream_close : glk_stream_close, glk_stream_set_position : glk_stream_set_position, glk_stream_get_position : glk_stream_get_position, glk_stream_set_current : glk_stream_set_current, glk_stream_get_current : glk_stream_get_current, glk_fileref_create_temp : glk_fileref_create_temp, glk_fileref_create_by_name : glk_fileref_create_by_name, glk_fileref_create_by_prompt : glk_fileref_create_by_prompt, glk_fileref_destroy : glk_fileref_destroy, glk_fileref_iterate : glk_fileref_iterate, glk_fileref_get_rock : glk_fileref_get_rock, glk_fileref_delete_file : glk_fileref_delete_file, glk_fileref_does_file_exist : glk_fileref_does_file_exist, glk_fileref_create_from_fileref : glk_fileref_create_from_fileref, glk_put_char : glk_put_char, glk_put_char_stream : glk_put_char_stream, glk_put_string : glk_put_string, glk_put_string_stream : glk_put_string_stream, glk_put_buffer : glk_put_buffer, glk_put_buffer_stream : glk_put_buffer_stream, glk_set_style : glk_set_style, glk_set_style_stream : glk_set_style_stream, glk_get_char_stream : glk_get_char_stream, glk_get_line_stream : glk_get_line_stream, glk_get_buffer_stream : glk_get_buffer_stream, glk_char_to_lower : glk_char_to_lower, glk_char_to_upper : glk_char_to_upper, glk_stylehint_set : glk_stylehint_set, glk_stylehint_clear : glk_stylehint_clear, glk_style_distinguish : glk_style_distinguish, glk_style_measure : glk_style_measure, glk_select : glk_select, glk_select_poll : glk_select_poll, glk_request_line_event : glk_request_line_event, glk_cancel_line_event : glk_cancel_line_event, glk_request_char_event : glk_request_char_event, glk_cancel_char_event : glk_cancel_char_event, glk_request_mouse_event : glk_request_mouse_event, glk_cancel_mouse_event : glk_cancel_mouse_event, glk_request_timer_events : glk_request_timer_events, glk_image_get_info : glk_image_get_info, glk_image_draw : glk_image_draw, glk_image_draw_scaled : glk_image_draw_scaled, glk_window_flow_break : glk_window_flow_break, glk_window_erase_rect : glk_window_erase_rect, glk_window_fill_rect : glk_window_fill_rect, glk_window_set_background_color : glk_window_set_background_color, glk_schannel_iterate : glk_schannel_iterate, glk_schannel_get_rock : glk_schannel_get_rock, glk_schannel_create : glk_schannel_create, glk_schannel_destroy : glk_schannel_destroy, glk_schannel_play : glk_schannel_play, glk_schannel_play_ext : glk_schannel_play_ext, glk_schannel_stop : glk_schannel_stop, glk_schannel_set_volume : glk_schannel_set_volume, glk_schannel_create_ext : glk_schannel_create_ext, glk_schannel_play_multi : glk_schannel_play_multi, glk_schannel_pause : glk_schannel_pause, glk_schannel_unpause : glk_schannel_unpause, glk_schannel_set_volume_ext : glk_schannel_set_volume_ext, glk_sound_load_hint : glk_sound_load_hint, glk_set_hyperlink : glk_set_hyperlink, glk_set_hyperlink_stream : glk_set_hyperlink_stream, glk_request_hyperlink_event : glk_request_hyperlink_event, glk_cancel_hyperlink_event : glk_cancel_hyperlink_event, glk_buffer_to_lower_case_uni : glk_buffer_to_lower_case_uni, glk_buffer_to_upper_case_uni : glk_buffer_to_upper_case_uni, glk_buffer_to_title_case_uni : glk_buffer_to_title_case_uni, glk_buffer_canon_decompose_uni : glk_buffer_canon_decompose_uni, glk_buffer_canon_normalize_uni : glk_buffer_canon_normalize_uni, glk_put_char_uni : glk_put_char_uni, glk_put_string_uni : glk_put_string_uni, glk_put_buffer_uni : glk_put_buffer_uni, glk_put_char_stream_uni : glk_put_char_stream_uni, glk_put_string_stream_uni : glk_put_string_stream_uni, glk_put_buffer_stream_uni : glk_put_buffer_stream_uni, glk_get_char_stream_uni : glk_get_char_stream_uni, glk_get_buffer_stream_uni : glk_get_buffer_stream_uni, glk_get_line_stream_uni : glk_get_line_stream_uni, glk_stream_open_file_uni : glk_stream_open_file_uni, glk_stream_open_memory_uni : glk_stream_open_memory_uni, glk_request_char_event_uni : glk_request_char_event_uni, glk_request_line_event_uni : glk_request_line_event_uni, glk_set_echo_line_event : glk_set_echo_line_event, glk_set_terminators_line_event : glk_set_terminators_line_event, glk_current_time : glk_current_time, glk_current_simple_time : glk_current_simple_time, glk_time_to_date_utc : glk_time_to_date_utc, glk_time_to_date_local : glk_time_to_date_local, glk_simple_time_to_date_utc : glk_simple_time_to_date_utc, glk_simple_time_to_date_local : glk_simple_time_to_date_local, glk_date_to_time_utc : glk_date_to_time_utc, glk_date_to_time_local : glk_date_to_time_local, glk_date_to_simple_time_utc : glk_date_to_simple_time_utc, glk_date_to_simple_time_local : glk_date_to_simple_time_local, glk_stream_open_resource : glk_stream_open_resource, glk_stream_open_resource_uni : glk_stream_open_resource_uni }; if (typeof module !== 'undefined' && module.exports) { module.exports = api; } return api; }(); /* End of Glk library. */