Compare commits

...

22 commits

Author SHA1 Message Date
9979e40d76 0.5.1 2023-05-22 00:36:16 +03:00
860498b8da Remove package.json import 2023-05-22 00:36:02 +03:00
1c0e29e4b7 Update README.md 2023-05-22 00:34:56 +03:00
ad163ef466
Merge pull request #1 from He4eT/emglken-0-5-2
Emglken 0.5.2
2023-05-21 00:19:32 +03:00
96fe7e7671 0.5.0 2023-05-21 00:16:31 +03:00
93db8c2224 Update README.md 2023-05-21 00:13:49 +03:00
e8ad18ffe4 Remove unused params 2023-05-21 00:10:20 +03:00
fcc3a3b296 Add eslint config file 2023-05-21 00:09:38 +03:00
3499c54ea5 Add eslint package 2023-05-21 00:08:58 +03:00
1e569811a9 Copy glkOte/glkapi from https://github.com/erkyrath/glkote 2023-05-21 00:07:37 +03:00
cb7d6e51f9 Use emglken 0.5.2 2023-05-20 23:39:01 +03:00
a594cb1f74 Update emglken to 0.5.2 2023-05-20 23:37:55 +03:00
c9d8c7acc1 0.4.1 2021-07-19 17:44:17 +05:00
74b8e7ef2f stdio: fix the fileref_prompt implementation 2021-07-19 17:44:05 +05:00
528e7ae729 fakeFS: use passed loggers 2021-07-19 17:42:44 +05:00
89f5caae5a 0.4.0 2021-07-19 00:44:41 +05:00
1c35fe3fec 0.3.2 2021-07-19 00:44:30 +05:00
15f54b48fa Update README.md 2021-07-19 00:44:13 +05:00
b51dc35e75 stdio: Add basic grid windows support 2021-07-19 00:21:48 +05:00
38e8e34ba8 stdio: Update the stdio implementation 2021-07-18 23:33:28 +05:00
10891cf269 cheapGlkOte: passing the full input data to onUpdateInputs 2021-07-18 23:31:57 +05:00
df05e82e8b 0.3.1 2021-07-12 19:17:48 +05:00
11 changed files with 2428 additions and 310 deletions

35
.eslintrc.json Normal file
View file

@ -0,0 +1,35 @@
{
"env": {
"browser": true,
"es2021": true,
"node": true
},
"extends": "eslint:recommended",
"overrides": [
],
"parserOptions": {
"ecmaVersion": "latest",
"sourceType": "module"
},
"ignorePatterns": [
"src/glkOte/*.js"
],
"rules": {
"indent": [
"error",
2
],
"linebreak-style": [
"error",
"unix"
],
"quotes": [
"error",
"single"
],
"semi": [
"error",
"never"
]
}
}

View file

@ -11,23 +11,24 @@ This repository includes examples of [stdio interface implementation](https://gi
### Initialization ### Initialization
```js ```js
const { glkInterface, sendFn } = const { Dialog, GlkOte, send } =
CheapGlkOte(handlers [, { loggers, size }]) CheapGlkOte(handlers [, { loggers, size }])
``` ```
### Input ### Input
```js ```js
sendFn('open door', windowObject) send('open door', inputType, targetWindow)
``` ```
You can received `windowObject` in `onUpdateWindows` handler.<br> You can obtain `inputType` and `id` of `targetWindow` inside the `onUpdateInputs` handler.<br>
You should respect input type setted by `onUpdateInputs`. You can specify `targetWindow` by its `id` inside the `onUpdateWindows` handler.<br>
As I know, `inputType` can be `line` or `char`.
### Output and lifecycle ### Output and lifecycle
```js ```js
const handlers = { const handlers = {
onInit: () => { onInit: () => {
/** /**
* It's time to prepare the user interface. * It's time to initialize the user interface.
*/ */
}, },
onUpdateWindows: windows => { onUpdateWindows: windows => {
@ -35,10 +36,11 @@ const handlers = {
* Game wants to change the number of windows. * Game wants to change the number of windows.
*/ */
}, },
onUpdateInputs: type => { onUpdateInputs: data => {
/** /**
* Game wants to change input type. * Game wants to change input type.
* Supported types: 'char', 'line'. * 'data' is a list with info about
* the target window and the input type.
*/ */
}, },
onUpdateContent: messages => { onUpdateContent: messages => {
@ -85,7 +87,7 @@ Default loggers:
const defaultLoggers = { const defaultLoggers = {
log: console.log, log: console.log,
warning: console.warn, warning: console.warn,
error: console.error error: console.error,
} }
``` ```
@ -94,7 +96,7 @@ Default sizes:
```js ```js
const defaultSize = { const defaultSize = {
width: 80, width: 80,
height: 25 height: 25,
} }
``` ```

View file

@ -4,11 +4,12 @@
* @see: https://github.com/curiousdannii/emglken/blob/master/bin/emglken.js * @see: https://github.com/curiousdannii/emglken/blob/master/bin/emglken.js
*/ */
const fs = require('fs') import { readFileSync } from 'fs'
const minimist = require('minimist') import minimist from 'minimist'
const CheapGlkOte = require('../src/') import CheapGlkOte from '../src/index.js'
const { handlers } = require('./stdio')
import { handlers } from './stdio.js'
const formats = [ const formats = [
{ {
@ -50,13 +51,19 @@ if (!format) {
process.exit(0) process.exit(0)
} }
const { glkInterface, sendFn } = CheapGlkOte(handlers) import(`emglken/src/${format.id}.js`)
handlers.setSend(sendFn) .then(({default: engine}) => engine)
.then((engine) => new engine())
.then((vm) => {
const cheapGlkOte = CheapGlkOte(handlers)
const engine = require('emglken/src/' + format.engine) handlers.setSend(cheapGlkOte.send)
const vm = new engine()
vm.prepare( vm.init(readFileSync(storyfile), {
fs.readFileSync(storyfile), Dialog: cheapGlkOte.Dialog,
glkInterface) GlkOte: cheapGlkOte.GlkOte,
vm.start() Glk: {},
wasmBinary: readFileSync(new URL(`../node_modules/emglken/build/${format.id}-core.wasm`, import.meta.url))
})
vm.start()
})

View file

@ -2,20 +2,22 @@
* @see: https://github.com/curiousdannii/glkote-term/blob/master/src/glkote-dumb.js * @see: https://github.com/curiousdannii/glkote-term/blob/master/src/glkote-dumb.js
*/ */
const readline = require('readline') import { createInterface, emitKeypressEvents } from 'readline'
const MuteStream = require('mute-stream') import MuteStream from 'mute-stream'
const ansiEscapes = require('ansi-escapes') import ansiEsc from 'ansi-escapes'
const stdin = process.stdin const stdin = process.stdin
const stdout = new MuteStream() const stdout = new MuteStream()
stdout.pipe(process.stdout) stdout.pipe(process.stdout)
const rl = readline.createInterface({ const rl = createInterface({
input: stdin, input: stdin,
output: stdout, output: stdout,
prompt: '' prompt: ''
}) })
let currentWindowId = null
let currentWindow = null let currentWindow = null
let currentInputType = null
let send = _ => _ let send = _ => _
@ -27,28 +29,38 @@ const onInit = () => {
if (stdin.isTTY) { if (stdin.isTTY) {
stdin.setRawMode(true) stdin.setRawMode(true)
} }
readline.emitKeypressEvents(stdin) emitKeypressEvents(stdin)
rl.resume() rl.resume()
clearScreen()
} }
const onUpdateWindows = windows => { const onUpdateWindows = windows => {
currentWindow = windows currentWindow = currentWindowId
.filter(x => x.type === 'buffer') ? windows
.slice(-1)[0] .find(x => x.id === currentWindowId)
: windows
.filter(x => x.type === 'buffer')
.slice(-1)[0]
} }
const onUpdateInputs = type => { const onUpdateInputs = data => {
type if (data.length === 0) return null
? attach_handlers(type) const { id, type } = data[0]
: detach_handlers()
currentWindowId = id
if (['char', 'line'].includes(type)) {
detach_handlers()
attach_handlers(type)
}
} }
const onUpdateContent = allMessages => { const clearScreen = () => {
const messages = allMessages.filter( stdout.write('\u001B[2J\u001B[0;0f')
content => content.id === currentWindow.id }
)[0]
return messages.text.forEach(({ append, content }) => { const drawBuffer = messages => {
messages.text.forEach(({ append, content }) => {
if (!append) { if (!append) {
stdout.write('\n') stdout.write('\n')
} }
@ -59,7 +71,7 @@ const onUpdateContent = allMessages => {
if (x.style === 'input') { if (x.style === 'input') {
if (stdout.isTTY) { if (stdout.isTTY) {
stdout.write(ansiEscapes.eraseLines(2)) stdout.write(ansiEsc.eraseLines(2))
stdout.write('> ') stdout.write('> ')
} }
} }
@ -73,10 +85,32 @@ const onUpdateContent = allMessages => {
}) })
} }
const onDisable = disable => const drawGrid = messages => {
disable clearScreen()
? detach_handlers() messages.lines
: attach_handlers() .map(x => x.content)
.map(([x]) => x)
.map(({text}) => text)
.map(x => x.trim())
.forEach(x => stdout.write(`${x}\n`))
}
const onUpdateContent = allMessages => {
detach_handlers()
const messages = allMessages.find(
content => content.id === currentWindow.id
)
;({
'buffer': drawBuffer,
'grid': drawGrid
})[currentWindow.type](messages)
}
const onDisable = disable => {
if (disable) detach_handlers()
}
const onExit = () => { const onExit = () => {
detach_handlers() detach_handlers()
@ -85,6 +119,8 @@ const onExit = () => {
} }
const onFileNameRequest = (tosave, usage, gameid, callback) => { const onFileNameRequest = (tosave, usage, gameid, callback) => {
stdout.write('\n')
stdout.write(gameid, tosave)
stdout.write('\n') stdout.write('\n')
rl.question( rl.question(
'Please enter a file name: ', 'Please enter a file name: ',
@ -93,10 +129,10 @@ const onFileNameRequest = (tosave, usage, gameid, callback) => {
: null)) : null))
} }
const onFileRead = (dirent, israw) => const onFileRead = (dirent) =>
void console.log('onFileRead:', dirent) void console.log('onFileRead:', dirent)
const onFileWrite = (dirent, content, israw) => const onFileWrite = (dirent, content) =>
void console.log('onFileWrite:', dirent, content.length) void console.log('onFileWrite:', dirent, content.length)
const handle_char_input = (str, key) => { const handle_char_input = (str, key) => {
@ -105,8 +141,6 @@ const handle_char_input = (str, key) => {
'\t': 'tab', '\t': 'tab',
} }
detach_handlers()
// Make sure this char isn't being remembered for the next line input // Make sure this char isn't being remembered for the next line input
rl._line_buffer = null rl._line_buffer = null
rl.line = '' rl.line = ''
@ -117,10 +151,12 @@ const handle_char_input = (str, key) => {
str || str ||
key.name.replace(/f(\d+)/, 'func$1') key.name.replace(/f(\d+)/, 'func$1')
send(res, currentWindow) send(res, currentInputType, currentWindow)
detach_handlers()
} }
const attach_handlers = type => { const attach_handlers = type => {
currentInputType = type
if (type === 'char') { if (type === 'char') {
stdout.mute() stdout.mute()
stdin.on('keypress', handle_char_input) stdin.on('keypress', handle_char_input)
@ -134,18 +170,18 @@ const detach_handlers = () => {
stdin.removeListener('keypress', handle_char_input) stdin.removeListener('keypress', handle_char_input)
rl.removeListener('line', handle_line_input) rl.removeListener('line', handle_line_input)
stdout.unmute() stdout.unmute()
currentInputType = null
} }
const handle_line_input = line => { const handle_line_input = line => {
if (stdout.isTTY) { if (stdout.isTTY) {
stdout.write(ansiEscapes.eraseLines(1)) stdout.write(ansiEsc.eraseLines(1))
} }
send(line, currentInputType, currentWindow)
detach_handlers() detach_handlers()
send(line, currentWindow)
} }
module.exports.handlers = { export const handlers = {
onInit, onInit,
onUpdateWindows, onUpdateWindows,
onUpdateInputs, onUpdateInputs,

1953
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -1,6 +1,6 @@
{ {
"name": "cheap-glkote", "name": "cheap-glkote",
"version": "0.3.0", "version": "0.5.1",
"description": "Abstract JavaScript implementation of GlkOte", "description": "Abstract JavaScript implementation of GlkOte",
"author": "He4eT <He4eTHb1u@gmail.com>", "author": "He4eT <He4eTHb1u@gmail.com>",
"license": "MIT", "license": "MIT",
@ -10,20 +10,26 @@
"interactive fiction", "interactive fiction",
"interactive-fiction" "interactive-fiction"
], ],
"type": "module",
"engines": {
"node": ">=14.0.0"
},
"main": "src/index.js", "main": "src/index.js",
"files": [ "files": [
"/src" "/src"
], ],
"repository": "https://github.com/He4eT/cheap-glkote", "repository": "https://github.com/He4eT/cheap-glkote",
"bugs": "https://github.com/He4eT/cheap-glkote/issues", "bugs": "https://github.com/He4eT/cheap-glkote/issues",
"dependencies": {},
"devDependencies": { "devDependencies": {
"ansi-escapes": "^4.0.0", "ansi-escapes": "^4.0.0",
"emglken": "^0.3.3", "emglken": "^0.5.2",
"eslint": "^8.41.0",
"minimist": "^1.2.5", "minimist": "^1.2.5",
"mute-stream": "0.0.8" "mute-stream": "0.0.8"
}, },
"scripts": { "scripts": {
"lint": "eslint bin/ src/",
"lint:fix": "eslint --fix bin/ src/",
"play": "node ./bin/player.stdio.js", "play": "node ./bin/player.stdio.js",
"test": "./tests/runtests.sh" "test": "./tests/runtests.sh"
} }

View file

@ -2,41 +2,34 @@
* @see: https://github.com/curiousdannii/glkote-term/blob/master/src/glkote-dumb.js * @see: https://github.com/curiousdannii/glkote-term/blob/master/src/glkote-dumb.js
*/ */
const GlkOte = require('./glkOte/glkote-term') import GlkOte from './glkOte/glkote-term.js'
class CheapGlkOte extends GlkOte { class CheapGlkOte extends GlkOte {
constructor(handlers, loggers, size) { constructor(handlers, loggers, size) {
super(size) super(size)
this.current_input_type = null
this.handlers = handlers this.handlers = handlers
this.loggers = loggers
} }
sendFn(message, window) { sendFn (message, type, window) {
this.send_response( this.send_response(
this.current_input_type, type,
window, window,
message) message)
this.current_input_type = null
} }
init(iface) { init (iface) {
this.handlers.onInit() this.handlers.onInit()
super.init(iface) super.init(iface)
} }
update_inputs(data) { update_inputs (data) {
if (!data.length) return null if (!data.length) return []
this.handlers.onUpdateInputs(data)
const { type } = data[0]
if (['char', 'line'].includes(type)) {
this.current_input_type = type
this.handlers.onUpdateInputs(type)
}
} }
accept_specialinput(data) { accept_specialinput (data) {
if (data.type === 'fileref_prompt') { if (data.type === 'fileref_prompt') {
const callback = ref => const callback = ref =>
this.send_response( this.send_response(
@ -53,44 +46,38 @@ class CheapGlkOte extends GlkOte {
} }
} }
update_content(messages) { update_content (messages) {
this.handlers.onUpdateContent(messages) this.handlers.onUpdateContent(messages)
} }
exit() { exit () {
this.handlers.onExit() this.handlers.onExit()
super.exit() super.exit()
} }
cancel_inputs(data) { cancel_inputs (data) {
if (data.length === 0) { this.handlers.onUpdateInputs(data)
this.current_input_type = null
this.handlers.onUpdateInputs(null)
}
} }
disable(disable) { disable (data) {
this.disabled = disable this.handlers.onDisable(data)
this.handlers.onDisable(disable)
} }
update_windows(windows) { update_windows (windows) {
if (windows.length) { this.handlers.onUpdateWindows(windows)
this.handlers.onUpdateWindows(windows)
}
} }
log(message) { log (message) {
loggers.log(message) this.loggers.log(message)
} }
warning(message) { warning (message) {
loggers.warn(message) this.loggers.warn(message)
} }
error(message) { error (message) {
loggers.error(message) this.loggers.error(message)
} }
} }
module.exports = CheapGlkOte export default CheapGlkOte

View file

@ -7,6 +7,7 @@ class FakeDialog {
constructor(handlers, loggers) { constructor(handlers, loggers) {
this.streaming = false this.streaming = false
this.handlers = handlers this.handlers = handlers
this.loggers = loggers
} }
file_ref_exists({ usage }) { file_ref_exists({ usage }) {
@ -15,11 +16,11 @@ class FakeDialog {
: false : false
} }
file_remove_ref (ref) { file_remove_ref () {
return true return true
} }
file_construct_ref(filename, usage, gameid) { file_construct_ref(filename, usage) {
return { return {
filename, filename,
usage: usage || '' usage: usage || ''
@ -40,16 +41,16 @@ class FakeDialog {
} }
log(message) { log(message) {
loggers.log(message) this.loggers.log(message)
} }
warning(message) { warning(message) {
loggers.warn(message) this.loggers.warn(message)
} }
error(message) { error(message) {
loggers.error(message) this.loggers.error(message)
} }
} }
module.exports = FakeDialog export default FakeDialog

View file

@ -1,10 +1,12 @@
'use strict';
/* GlkAPI -- a Javascript Glk API for IF interfaces /* GlkAPI -- a Javascript Glk API for IF interfaces
* GlkOte Library: version 2.2.3. * GlkOte Library: version 2.3.2.
* Glk API which this implements: version 0.7.4. * Glk API which this implements: version 0.7.4.
* Designed by Andrew Plotkin <erkyrath@eblong.com> * Designed by Andrew Plotkin <erkyrath@eblong.com>
* <http://eblong.com/zarf/glk/glkote.html> * <http://eblong.com/zarf/glk/glkote.html>
* *
* This Javascript library is copyright 2010-16 by Andrew Plotkin. * This Javascript library is copyright 2010-20 by Andrew Plotkin.
* It is distributed under the MIT license; see the "LICENSE" file. * 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 * This file is a Glk API compatibility layer for glkote.js. It offers a
@ -38,21 +40,16 @@
and will also double the write-count in a stream. and will also double the write-count in a stream.
*/ */
/* Put everything inside the Glk namespace. */ /* All state is contained in GlkClass. */
var GlkClass = function() {
Glk = function() { var GlkOte = null; /* imported API object */
var VM = null; /* imported API object (the VM interface) */
var GiDispa = null; /* imported API object (the dispatch layer) */
var Blorb = null; /* imported API object (the resource layer) */
/* The VM interface object. */ /* Environment capabilities. (Checked at init time.) */
var VM = null; var has_canvas;
/* References to external libraries */
var Dialog;
var GiDispa;
var GiLoad;
var GlkOte;
/* Environment capabilities */
var support = {};
/* Options from the vm_options object. */ /* Options from the vm_options object. */
var option_exit_warning; var option_exit_warning;
@ -70,61 +67,7 @@ var event_generation = 0;
var current_partial_inputs = null; var current_partial_inputs = null;
var current_partial_outputs = null; var current_partial_outputs = null;
// Set external variable references /* Initialize the library, initialize the VM, and set it running. (It will
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.) 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 The vm_options argument must have a vm_options.vm field, which must be an
@ -139,17 +82,27 @@ function set_references( vm_options )
library sets that up for you.) library sets that up for you.)
*/ */
function init(vm_options) { function init(vm_options) {
/* Set references to external libraries */ /* Either GlkOte was passed in or we must create one. */
set_references( vm_options ); if (vm_options.GlkOte) {
GlkOte = vm_options.GlkOte;
}
else if (window.GlkOteClass) {
GlkOte = new window.GlkOteClass();
}
/* Either Blorb was passed in or we don't have one. */
if (vm_options.Blorb) {
Blorb = vm_options.Blorb;
}
/* Check for canvas support. We don't rely on jquery here. */
has_canvas = (document.createElement('canvas').getContext != undefined);
VM = vm_options.vm; VM = vm_options.vm;
if (GiDispa) GiDispa = vm_options.GiDispa; /* may be null/undefined */
GiDispa.set_vm(VM);
vm_options.accept = accept_ui_event; vm_options.accept = accept_ui_event;
GlkOte.init(vm_options);
option_exit_warning = vm_options.exit_warning; option_exit_warning = vm_options.exit_warning;
option_do_vm_autosave = vm_options.do_vm_autosave; option_do_vm_autosave = vm_options.do_vm_autosave;
option_before_select_hook = vm_options.before_select_hook; option_before_select_hook = vm_options.before_select_hook;
@ -159,6 +112,16 @@ function init(vm_options) {
if (option_before_select_hook) { if (option_before_select_hook) {
option_before_select_hook(); option_before_select_hook();
} }
/* Initialize the lower levels. */
if (GiDispa)
GiDispa.init({ io:vm_options.io, vm:vm_options.vm });
GlkOte.init(vm_options);
}
function is_inited() {
return (VM != null && GlkOte != null);
} }
function accept_ui_event(obj) { function accept_ui_event(obj) {
@ -184,12 +147,10 @@ function accept_ui_event(obj) {
switch (obj.type) { switch (obj.type) {
case 'init': case 'init':
content_metrics = obj.metrics; content_metrics = complete_metrics(obj.metrics);
/* Process the support array */ /* We ignore the support array. This library is updated in sync
if (obj.support) { with GlkOte, so we know what it supports. */
obj.support.forEach(function(item) {support[item] = 1;}); VM.start();
}
VM.init();
break; break;
case 'external': case 'external':
@ -231,7 +192,7 @@ function accept_ui_event(obj) {
break; break;
case 'arrange': case 'arrange':
content_metrics = obj.metrics; content_metrics = complete_metrics(obj.metrics);
box = { box = {
left: content_metrics.outspacingx, left: content_metrics.outspacingx,
top: content_metrics.outspacingy, top: content_metrics.outspacingy,
@ -255,6 +216,134 @@ function accept_ui_event(obj) {
} }
} }
/* Given a partial metrics object, return one with all the required
values. Missing values will default to 0 or the standard inherited
terms. (E.g., if "inspacingx" is missing it will default to
"inspacing", then "spacing", then 0. See measure_window() in
glkote.js or data_metrics_parse() in RemGlk.)
All values in the given object will be copied over; defaulting only
applies to missing values from the required set.
*/
function complete_metrics(metrics) {
// Default values if absolutely nothing is specified.
var res = {
width: 80,
height: 50,
gridcharwidth: 1,
gridcharheight: 1,
buffercharwidth: 1,
buffercharheight: 1,
gridmarginx: 0,
gridmarginy: 0,
buffermarginx: 0,
buffermarginy: 0,
graphicsmarginx: 0,
graphicsmarginy: 0,
outspacingx: 0,
outspacingy: 0,
inspacingx: 0,
inspacingy: 0,
};
// Various ways of specifying defaults.
var val;
val = metrics.charwidth;
if (val !== undefined) {
res.gridcharwidth = val;
res.buffercharwidth = val;
}
val = metrics.charheight;
if (val !== undefined) {
res.gridcharheight = val;
res.buffercharheight = val;
}
val = metrics.margin;
if (val !== undefined) {
res.gridmarginx = val;
res.gridmarginy = val;
res.buffermarginx = val;
res.buffermarginy = val;
res.graphicsmarginx = val;
res.graphicsmarginy = val;
}
val = metrics.gridmargin;
if (val !== undefined) {
res.gridmarginx = val;
res.gridmarginy = val;
}
val = metrics.buffermargin;
if (val !== undefined) {
res.buffermarginx = val;
res.buffermarginy = val;
}
val = metrics.graphicsmargin;
if (val !== undefined) {
res.graphicsmarginx = val;
res.graphicsmarginy = val;
}
val = metrics.marginx;
if (val !== undefined) {
res.gridmarginx = val;
res.buffermarginx = val;
res.graphicsmarginx = val;
}
val = metrics.marginy;
if (val !== undefined) {
res.gridmarginy = val;
res.buffermarginy = val;
res.graphicsmarginy = val;
}
val = metrics.spacing;
if (val !== undefined) {
res.inspacingx = val;
res.inspacingy = val;
res.outspacingx = val;
res.outspacingy = val;
}
val = metrics.inspacing;
if (val !== undefined) {
res.inspacingx = val;
res.inspacingy = val;
}
val = metrics.outspacing;
if (val !== undefined) {
res.outspacingx = val;
res.outspacingy = val;
}
val = metrics.spacingx;
if (val !== undefined) {
res.inspacingx = val;
res.outspacingx = val;
}
val = metrics.spacingy;
if (val !== undefined) {
res.inspacingy = val;
res.outspacingy = val;
}
// Copy over all the supplied fields. These override the defaults above.
res = Object.assign(res, metrics);
return res;
}
function handle_arrange_input() { function handle_arrange_input() {
if (!gli_selectref) if (!gli_selectref)
return; return;
@ -453,8 +542,8 @@ function handle_line_input(disprock, input, termkey) {
VM.resume(); VM.resume();
} }
function update(type) { function update() {
var dataobj = { type: type || 'update', gen: event_generation }; var dataobj = { type: 'update', gen: event_generation };
var winarray = null; var winarray = null;
var contentarray = null; var contentarray = null;
var inputarray = null; var inputarray = null;
@ -705,10 +794,27 @@ function update(type) {
} }
} }
/* Return the library interface object that we were passed or created.
Call this if you want to use, e.g., the same Dialog object that GlkOte
is using.
*/
function get_library(val) {
switch (val) {
case 'VM': return VM;
case 'GlkOte': return GlkOte;
case 'GiDispa': return GiDispa;
case 'Blorb': return Blorb;
case 'Dialog': return GlkOte.getlibrary('Dialog');
}
/* Unrecognized library name. */
return null;
}
/* Wrap up the current display state as a (JSONable) object. This is /* Wrap up the current display state as a (JSONable) object. This is
called from Quixe.vm_autosave. called from Quixe.vm_autosave.
*/ */
function save_allstate() { function save_allstate() {
var Dialog = GlkOte.getlibrary('Dialog');
var res = {}; var res = {};
if (gli_rootwin) if (gli_rootwin)
@ -886,6 +992,8 @@ function save_allstate() {
*/ */
function restore_allstate(res) function restore_allstate(res)
{ {
var Dialog = GlkOte.getlibrary('Dialog');
if (gli_windowlist || gli_streamlist || gli_filereflist) if (gli_windowlist || gli_streamlist || gli_filereflist)
throw('restore_allstate: glkapi module has already been launched'); throw('restore_allstate: glkapi module has already been launched');
@ -1058,7 +1166,7 @@ function restore_allstate(res)
case strtype_Resource: case strtype_Resource:
str.resfilenum = obj.resfilenum; str.resfilenum = obj.resfilenum;
var el = GiLoad.find_data_chunk(str.resfilenum); var el = Blorb.get_data_chunk(str.resfilenum);
if (el) { if (el) {
str.buf = el.data; str.buf = el.data;
} }
@ -1143,6 +1251,11 @@ function restore_allstate(res)
function fatal_error(msg) { function fatal_error(msg) {
has_exited = true; has_exited = true;
ui_disabled = true; ui_disabled = true;
if (!GlkOte) {
// We haven't been initialized yet, so we can only try to log the error and hope someone sees it.
console.log('Fatal error:', msg);
return;
}
GlkOte.error(msg); GlkOte.error(msg);
var dataobj = { type: 'update', gen: event_generation, disable: true }; var dataobj = { type: 'update', gen: event_generation, disable: true };
dataobj.input = []; dataobj.input = [];
@ -2693,8 +2806,10 @@ function UniArrayToBE32(arr) {
up in Safari, in Opera, and in Firefox if you have Firebug installed.) up in Safari, in Opera, and in Firefox if you have Firebug installed.)
*/ */
function qlog(msg) { function qlog(msg) {
if (typeof console !== 'undefined' && console.log) if (window.console && console.log)
console.log(msg); console.log(msg);
else if (window.opera && opera.postError)
opera.postError(msg);
} }
/* RefBox: Simple class used for "call-by-reference" Glk arguments. The object /* RefBox: Simple class used for "call-by-reference" Glk arguments. The object
@ -2896,13 +3011,13 @@ function gli_window_put_string(win, val) {
gli_window_grid_canonicalize(), but I've inlined it. */ gli_window_grid_canonicalize(), but I've inlined it. */
if (win.cursorx < 0) if (win.cursorx < 0)
win.cursorx = 0; win.cursorx = 0;
else if (win.cursorx >= win.gridwidth) { if (win.cursorx >= win.gridwidth) {
win.cursorx = 0; win.cursorx = 0;
win.cursory++; win.cursory++;
} }
if (win.cursory < 0) if (win.cursory < 0)
win.cursory = 0; win.cursory = 0;
else if (win.cursory >= win.gridheight) if (win.cursory >= win.gridheight)
break; /* outside the window */ break; /* outside the window */
if (ch == "\n") { if (ch == "\n") {
@ -2912,7 +3027,7 @@ function gli_window_put_string(win, val) {
continue; continue;
} }
lineobj = win.lines[win.cursory]; var lineobj = win.lines[win.cursory];
lineobj.dirty = true; lineobj.dirty = true;
lineobj.chars[win.cursorx] = ch; lineobj.chars[win.cursorx] = ch;
lineobj.styles[win.cursorx] = win.style; lineobj.styles[win.cursorx] = win.style;
@ -2935,13 +3050,13 @@ function gli_window_put_string(win, val) {
function gli_window_grid_canonicalize(win) { function gli_window_grid_canonicalize(win) {
if (win.cursorx < 0) if (win.cursorx < 0)
win.cursorx = 0; win.cursorx = 0;
else if (win.cursorx >= win.gridwidth) { if (win.cursorx >= win.gridwidth) {
win.cursorx = 0; win.cursorx = 0;
win.cursory++; win.cursory++;
} }
if (win.cursory < 0) if (win.cursory < 0)
win.cursory = 0; win.cursory = 0;
else if (win.cursory >= win.gridheight) if (win.cursory >= win.gridheight)
return true; /* outside the window */ return true; /* outside the window */
return false; return false;
} }
@ -3094,7 +3209,7 @@ function gli_window_close(win, recurse) {
function gli_window_rearrange(win, box) { function gli_window_rearrange(win, box) {
var width, height, oldwidth, oldheight; var width, height, oldwidth, oldheight;
var min, max, diff, splitwid, ix, cx, lineobj; var min, max, diff, split, splitwid, ix, cx, lineobj;
var box1, box2, ch1, ch2; var box1, box2, ch1, ch2;
geometry_changed = true; geometry_changed = true;
@ -3380,6 +3495,8 @@ function gli_stream_dirty_file(str) {
buffer out. buffer out.
*/ */
function gli_stream_flush_file(str) { function gli_stream_flush_file(str) {
var Dialog = GlkOte.getlibrary('Dialog');
if (str.streaming) if (str.streaming)
GlkOte.log('### gli_stream_flush_file called for streaming file!'); GlkOte.log('### gli_stream_flush_file called for streaming file!');
if (!(str.timer_id === null)) { if (!(str.timer_id === null)) {
@ -3390,6 +3507,8 @@ function gli_stream_flush_file(str) {
} }
function gli_new_fileref(filename, usage, rock, ref) { function gli_new_fileref(filename, usage, rock, ref) {
var Dialog = GlkOte.getlibrary('Dialog');
var fref = {}; var fref = {};
fref.filename = filename; fref.filename = filename;
fref.rock = rock; fref.rock = rock;
@ -3510,7 +3629,7 @@ function gli_put_char(str, ch) {
var len = arr.length; var len = arr.length;
if (len > str.buflen-str.bufpos) if (len > str.buflen-str.bufpos)
len = str.buflen-str.bufpos; len = str.buflen-str.bufpos;
for (ix=0; ix<len; ix++) for (var ix=0; ix<len; ix++)
str.buf[str.bufpos+ix] = arr[ix]; str.buf[str.bufpos+ix] = arr[ix];
str.bufpos += len; str.bufpos += len;
if (str.bufpos > str.bufeof) if (str.bufpos > str.bufeof)
@ -3813,6 +3932,7 @@ function gli_get_line(str, buf, want_unicode) {
return 0; return 0;
var len = buf.length; var len = buf.length;
var lx, ch;
var gotnewline; var gotnewline;
switch (str.type) { switch (str.type) {
@ -4076,7 +4196,6 @@ function glk_exit() {
gli_selectref = null; gli_selectref = null;
if (option_exit_warning) if (option_exit_warning)
GlkOte.warning(option_exit_warning); GlkOte.warning(option_exit_warning);
update('exit');
return DidNotReturn; return DidNotReturn;
} }
@ -4133,20 +4252,22 @@ function glk_gestalt_ext(sel, val, arr) {
return 2; // gestalt_CharOutput_ExactPrint return 2; // gestalt_CharOutput_ExactPrint
case 4: // gestalt_MouseInput case 4: // gestalt_MouseInput
if (val == Const.wintype_TextGrid) if (val == Const.wintype_TextBuffer)
return 1; return 1;
if (support.graphics && val == Const.wintype_Graphics) if (val == Const.wintype_Graphics && has_canvas)
return 1; return 1;
return 0; return 0;
case 5: // gestalt_Timer case 5: // gestalt_Timer
return support.timer || 0; return 1;
case 6: // gestalt_Graphics case 6: // gestalt_Graphics
return support.graphics || 0; return 1;
case 7: // gestalt_DrawImage case 7: // gestalt_DrawImage
if (support.graphics && (val == Const.wintype_TextBuffer || val == Const.wintype_Graphics)) if (val == Const.wintype_TextBuffer)
return 1;
if (val == Const.wintype_Graphics && has_canvas)
return 1; return 1;
return 0; return 0;
@ -4160,10 +4281,10 @@ function glk_gestalt_ext(sel, val, arr) {
return 0; return 0;
case 11: // gestalt_Hyperlinks case 11: // gestalt_Hyperlinks
return support.hyperlinks || 0; return 1;
case 12: // gestalt_HyperlinkInput case 12: // gestalt_HyperlinkInput
if (support.hyperlinks && (val == Const.wintype_TextBuffer || val == Const.wintype_TextGrid)) if (val == 3 || val == 4) // TextBuffer or TextGrid
return 1; return 1;
else else
return 0; return 0;
@ -4172,7 +4293,7 @@ function glk_gestalt_ext(sel, val, arr) {
return 0; return 0;
case 14: // gestalt_GraphicsTransparency case 14: // gestalt_GraphicsTransparency
return support.graphics || 0; return 1;
case 15: // gestalt_Unicode case 15: // gestalt_Unicode
return 1; return 1;
@ -4311,7 +4432,7 @@ function glk_window_open(splitwin, method, size, wintype, rock) {
newwin.cursory = 0; newwin.cursory = 0;
break; break;
case Const.wintype_Graphics: case Const.wintype_Graphics:
if (!support.graphics) { if (!has_canvas) {
/* Graphics windows not supported; silently return null */ /* Graphics windows not supported; silently return null */
gli_delete_window(newwin); gli_delete_window(newwin);
return null; return null;
@ -4691,6 +4812,8 @@ function glk_stream_get_rock(str) {
} }
function glk_stream_open_file(fref, fmode, rock) { function glk_stream_open_file(fref, fmode, rock) {
var Dialog = GlkOte.getlibrary('Dialog');
if (!fref) if (!fref)
throw('glk_stream_open_file: invalid fileref'); throw('glk_stream_open_file: invalid fileref');
@ -4793,21 +4916,20 @@ function glk_stream_open_memory(buf, fmode, rock) {
function glk_stream_open_resource(filenum, rock) { function glk_stream_open_resource(filenum, rock) {
var str; var str;
if (!GiLoad || !GiLoad.find_data_chunk) if (!Blorb)
return null; return null;
var el = GiLoad.find_data_chunk(filenum); var el = Blorb.get_data_chunk(filenum);
if (!el) if (!el)
return null; return null;
var buf = el.data; var buf = el.data;
var isbinary = (el.type == 'BINA');
str = gli_new_stream(strtype_Resource, str = gli_new_stream(strtype_Resource,
true, true,
false, false,
rock); rock);
str.unicode = false; str.unicode = false;
str.isbinary = isbinary; str.isbinary = el.binary;
str.resfilenum = filenum; str.resfilenum = filenum;
@ -4832,21 +4954,20 @@ function glk_stream_open_resource(filenum, rock) {
function glk_stream_open_resource_uni(filenum, rock) { function glk_stream_open_resource_uni(filenum, rock) {
var str; var str;
if (!GiLoad || !GiLoad.find_data_chunk) if (!Blorb)
return null; return null;
var el = GiLoad.find_data_chunk(filenum); var el = Blorb.get_data_chunk(filenum);
if (!el) if (!el)
return null; return null;
var buf = el.data; var buf = el.data;
var isbinary = (el.type == 'BINA');
str = gli_new_stream(strtype_Resource, str = gli_new_stream(strtype_Resource,
true, true,
false, false,
rock); rock);
str.unicode = true; str.unicode = true;
str.isbinary = isbinary; str.isbinary = el.binary;
str.resfilenum = filenum; str.resfilenum = filenum;
@ -4869,6 +4990,8 @@ function glk_stream_open_resource_uni(filenum, rock) {
} }
function glk_stream_close(str, result) { function glk_stream_close(str, result) {
var Dialog = GlkOte.getlibrary('Dialog');
if (!str) if (!str)
throw('glk_stream_close: invalid stream'); throw('glk_stream_close: invalid stream');
@ -4949,18 +5072,21 @@ function glk_stream_get_current() {
} }
function glk_fileref_create_temp(usage, rock) { function glk_fileref_create_temp(usage, rock) {
var Dialog = GlkOte.getlibrary('Dialog');
var filetype = (usage & Const.fileusage_TypeMask); var filetype = (usage & Const.fileusage_TypeMask);
var filetypename = FileTypeMap[filetype]; var filetypename = FileTypeMap[filetype];
var ref = Dialog.file_construct_temp_ref(filetypename); var ref = Dialog.file_construct_temp_ref(filetypename);
fref = gli_new_fileref(ref.filename, usage, rock, ref); var fref = gli_new_fileref(ref.filename, usage, rock, ref);
return fref; return fref;
} }
function glk_fileref_create_by_name(usage, filename, rock) { function glk_fileref_create_by_name(usage, filename, rock) {
var Dialog = GlkOte.getlibrary('Dialog');
/* Filenames that do not come from the user must be cleaned up. */ /* Filenames that do not come from the user must be cleaned up. */
filename = Dialog.file_clean_fixed_name(filename, (usage & Const.fileusage_TypeMask)); filename = Dialog.file_clean_fixed_name(filename, (usage & Const.fileusage_TypeMask));
fref = gli_new_fileref(filename, usage, rock, null); var fref = gli_new_fileref(filename, usage, rock, null);
return fref; return fref;
} }
@ -5010,19 +5136,19 @@ function glk_fileref_create_by_prompt(usage, fmode, rock) {
function gli_fileref_create_by_prompt_callback(obj) { function gli_fileref_create_by_prompt_callback(obj) {
var ref = obj.value; var ref = obj.value;
/* This "value" field will be a dialog.js fileref object if we are
connected to GlkOte. However, if we are connected to RegTest,
it will be a plain string. We attempt to handle both cases. */
var usage = ui_specialcallback.usage; var usage = ui_specialcallback.usage;
var rock = ui_specialcallback.rock; var rock = ui_specialcallback.rock;
var fref = null; var fref = null;
if (ref) { if (ref && typeof(ref) == 'object') {
fref = gli_new_fileref(ref.filename, usage, rock, ref); fref = gli_new_fileref(ref.filename, usage, rock, ref);
} }
else if (ref && typeof(ref) == 'string') {
// If reading a file which doesn't exist, return null fref = gli_new_fileref(ref, usage, rock, null);
if ( ui_specialinput.filemode === 'read' && !Dialog.file_ref_exists( fref.ref ) )
{
glk_fileref_destroy( fref );
fref = null;
} }
ui_specialinput = null; ui_specialinput = null;
@ -5063,12 +5189,14 @@ function glk_fileref_get_rock(fref) {
} }
function glk_fileref_delete_file(fref) { function glk_fileref_delete_file(fref) {
var Dialog = GlkOte.getlibrary('Dialog');
if (!fref) if (!fref)
throw('glk_fileref_delete_file: invalid fileref'); throw('glk_fileref_delete_file: invalid fileref');
Dialog.file_remove_ref(fref.ref); Dialog.file_remove_ref(fref.ref);
} }
function glk_fileref_does_file_exist(fref) { function glk_fileref_does_file_exist(fref) {
var Dialog = GlkOte.getlibrary('Dialog');
if (!fref) if (!fref)
throw('glk_fileref_does_file_exist: invalid fileref'); throw('glk_fileref_does_file_exist: invalid fileref');
if (Dialog.file_ref_exists(fref.ref)) if (Dialog.file_ref_exists(fref.ref))
@ -5369,10 +5497,10 @@ function glk_request_timer_events(msec) {
/* Graphics functions. */ /* Graphics functions. */
function glk_image_get_info(imgid, widthref, heightref) { function glk_image_get_info(imgid, widthref, heightref) {
if (!GiLoad || !GiLoad.get_image_info) if (!Blorb || !Blorb.get_image_info)
return null; return null;
var info = GiLoad.get_image_info(imgid); var info = Blorb.get_image_info(imgid);
if (info) { if (info) {
if (widthref) if (widthref)
widthref.set_value(info.width); widthref.set_value(info.width);
@ -5391,9 +5519,9 @@ function glk_image_draw(win, imgid, val1, val2) {
if (!win) if (!win)
throw('glk_image_draw: invalid window'); throw('glk_image_draw: invalid window');
if (!GiLoad || !GiLoad.get_image_info) if (!Blorb || !Blorb.get_image_info)
return 0; return 0;
var info = GiLoad.get_image_info(imgid); var info = Blorb.get_image_info(imgid);
if (!info) if (!info)
return 0; return 0;
@ -5441,9 +5569,9 @@ function glk_image_draw_scaled(win, imgid, val1, val2, width, height) {
if (!win) if (!win)
throw('glk_image_draw_scaled: invalid window'); throw('glk_image_draw_scaled: invalid window');
if (!GiLoad || !GiLoad.get_image_info) if (!Blorb || !Blorb.get_image_info)
return 0; return 0;
var info = GiLoad.get_image_info(imgid); var info = Blorb.get_image_info(imgid);
if (!info) if (!info)
return 0; return 0;
@ -5954,6 +6082,8 @@ function glk_get_line_stream_uni(str, buf) {
} }
function glk_stream_open_file_uni(fref, fmode, rock) { function glk_stream_open_file_uni(fref, fmode, rock) {
var Dialog = GlkOte.getlibrary('Dialog');
if (!fref) if (!fref)
throw('glk_stream_open_file_uni: invalid fileref'); throw('glk_stream_open_file_uni: invalid fileref');
@ -6241,11 +6371,13 @@ function glk_date_to_simple_time_local(dateref, factor) {
/* End of Glk namespace function. Return the object which will /* End of Glk namespace function. Return the object which will
become the Glk global. */ become the Glk global. */
var api = { return {
version: '2.2.3', /* GlkOte/GlkApi version */ classname: 'Glk',
set_references: set_references, version: '2.3.2', /* GlkOte/GlkApi version */
init : init, init : init,
inited : is_inited,
update : update, update : update,
getlibrary : get_library,
save_allstate : save_allstate, save_allstate : save_allstate,
restore_allstate : restore_allstate, restore_allstate : restore_allstate,
fatal_error : fatal_error, fatal_error : fatal_error,
@ -6385,12 +6517,12 @@ var api = {
glk_stream_open_resource_uni : glk_stream_open_resource_uni glk_stream_open_resource_uni : glk_stream_open_resource_uni
}; };
if (typeof module !== 'undefined' && module.exports) { };
module.exports = api;
}
return api; /* Glk is an instance of GlkClass, ready to init. */
var Glk = new GlkClass();
}(); // Node-compatible behavior
try { exports.Glk = Glk; exports.GlkClass = GlkClass; } catch (ex) {};
/* End of Glk library. */ /* End of Glk library. */

View file

@ -11,7 +11,7 @@ class GlkOte {
this.disabled = false this.disabled = false
this.generation = 0 this.generation = 0
this.interface = null this.interface = null
this.version = require('../../package.json').version this.version = '0.5.1'
} }
measure_window() { measure_window() {
@ -149,4 +149,4 @@ class GlkOte {
} }
} }
module.exports = GlkOte export default GlkOte

View file

@ -1,5 +1,5 @@
const FakeDialog = require('./fakeDialog') import FakeDialog from './fakeDialog.js'
const CheapGlkOte = require('./cheapGlkOte') import CheapGlkOte from './cheapGlkOte.js'
const noop = () => void null const noop = () => void null
@ -12,39 +12,36 @@ const defaultHandlers = [
'onFileNameRequest', 'onFileNameRequest',
'onFileRead', 'onFileRead',
'onFileWrite', 'onFileWrite',
'onExit' 'onExit',
].reduce((acc, x) => ((acc[x] = noop), acc), {}) ].reduce((acc, x) => ((acc[x] = noop), acc), {})
const defaultLoggers = { const defaultLoggers = {
log: console.log, log: console.log,
warning: console.warn, warning: console.warn,
error: console.error error: console.error,
} }
const defaultSize = { const defaultSize = {
width: 80, width: 80,
height: 25 height: 25,
} }
module.exports = (handlers_, {loggers: loggers_, size: size_ } = {}) => { export default (handlers_, {loggers: loggers_, size: size_ } = {}) => {
const handlers = const handlers =
Object.assign({}, defaultHandlers, handlers_) Object.assign({}, defaultHandlers, handlers_)
const loggers = const loggers =
Object.assign({}, defaultLoggers, size_) Object.assign({}, defaultLoggers, loggers_)
const size = const size =
Object.assign({}, defaultSize, size_) Object.assign({}, defaultSize, size_)
const Dialog = new FakeDialog(handlers, loggers) const Dialog = new FakeDialog(handlers, loggers)
const GlkOte = new CheapGlkOte(handlers, loggers, size) const GlkOte = new CheapGlkOte(handlers, loggers, size)
const sendFn = GlkOte.sendFn.bind(GlkOte) const send = GlkOte.sendFn.bind(GlkOte)
return { return {
sendFn, Dialog,
glkInterface: { GlkOte,
Dialog, send,
GlkOte,
Glk: {}
}
} }
} }