├── .gitignore ├── jsonvalidate.py ├── rgwin_blank.h ├── glkstart.c ├── rgblorb.c ├── rgwin_graph.h ├── rgwin_blank.c ├── LICENSE ├── gi_debug.c ├── rgwin_pair.h ├── Makefile ├── README.txt ├── rgstyle.c ├── rgdata_int.h ├── rgwin_grid.h ├── rgwin_buf.h ├── rgschan.c ├── gi_blorb.h ├── gi_dispa.h ├── rggestal.c ├── glkstart.h ├── rgwin_pair.c ├── rgwin_graph.c ├── rgmisc.c ├── cgdate.c ├── gi_debug.h ├── rgdata.h ├── remglk.h ├── rgevent.c ├── rgfref.c ├── rgwin_grid.c ├── docs.html ├── cgunicod.c ├── glk.h └── rgwin_buf.c /.gitignore: -------------------------------------------------------------------------------- 1 | *.o 2 | *.a 3 | Make.* 4 | -------------------------------------------------------------------------------- /jsonvalidate.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # Read a stream of JSON from stdin. Display each one as it is read. 4 | # If a syntax error occurs, it won't actually be detected; the output 5 | # will just freeze up. 6 | 7 | import sys 8 | import json 9 | 10 | dec = json.JSONDecoder() 11 | 12 | dat = '' 13 | 14 | while True: 15 | ln = sys.stdin.readline() 16 | if (not ln): 17 | break 18 | dat = dat + ln 19 | try: 20 | (obj, pos) = dec.raw_decode(dat) 21 | dat = dat[ pos : ] 22 | print(obj) 23 | except: 24 | pass 25 | -------------------------------------------------------------------------------- /rgwin_blank.h: -------------------------------------------------------------------------------- 1 | /* rgwin_blank.h: The blank window header file 2 | for RemGlk, remote-procedure-call implementation of the Glk API. 3 | Designed by Andrew Plotkin 4 | http://eblong.com/zarf/glk/ 5 | */ 6 | 7 | typedef struct window_blank_struct { 8 | window_t *owner; 9 | } window_blank_t; 10 | 11 | extern window_blank_t *win_blank_create(window_t *win); 12 | extern void win_blank_destroy(window_blank_t *dwin); 13 | extern void win_blank_rearrange(window_t *win, grect_t *box, data_metrics_t *metrics); 14 | extern void win_blank_redraw(window_t *win); 15 | -------------------------------------------------------------------------------- /glkstart.c: -------------------------------------------------------------------------------- 1 | /* glkstart.c: Unix-specific startup code -- sample file. 2 | Designed by Andrew Plotkin 3 | http://eblong.com/zarf/glk/ 4 | 5 | This is Unix startup code for the simplest possible kind of Glk 6 | program -- no command-line arguments; no startup files; no nothing. 7 | 8 | Remember, this is a sample file. You should copy it into the Glk 9 | program you are compiling, and modify it to your needs. This should 10 | *not* be compiled into the Glk library itself. 11 | */ 12 | 13 | #include "glk.h" 14 | #include "glkstart.h" 15 | 16 | glkunix_argumentlist_t glkunix_arguments[] = { 17 | { NULL, glkunix_arg_End, NULL } 18 | }; 19 | 20 | int glkunix_startup_code(glkunix_startup_t *data) 21 | { 22 | return TRUE; 23 | } 24 | 25 | -------------------------------------------------------------------------------- /rgblorb.c: -------------------------------------------------------------------------------- 1 | #include "glk.h" 2 | #include "gi_blorb.h" 3 | 4 | /* We'd like to be able to deal with game files in Blorb files, even 5 | if we never load a sound or image. We'd also like to be able to 6 | deal with Data chunks. So we're willing to set a map here. */ 7 | 8 | static giblorb_map_t *blorbmap = 0; /* NULL */ 9 | 10 | giblorb_err_t giblorb_set_resource_map(strid_t file) 11 | { 12 | giblorb_err_t err; 13 | 14 | err = giblorb_create_map(file, &blorbmap); 15 | if (err) { 16 | blorbmap = 0; /* NULL */ 17 | return err; 18 | } 19 | 20 | return giblorb_err_None; 21 | } 22 | 23 | giblorb_err_t giblorb_unset_resource_map() 24 | { 25 | if (blorbmap) { 26 | giblorb_err_t err; 27 | err = giblorb_destroy_map(blorbmap); 28 | if (err) { 29 | return err; 30 | } 31 | blorbmap = 0; /* NULL */ 32 | } 33 | 34 | return giblorb_err_None; 35 | } 36 | 37 | giblorb_map_t *giblorb_get_resource_map() 38 | { 39 | return blorbmap; 40 | } 41 | -------------------------------------------------------------------------------- /rgwin_graph.h: -------------------------------------------------------------------------------- 1 | /* rgwin_graph.h: The graphics window header 2 | for RemGlk, remote-procedure-call implementation of the Glk API. 3 | Designed by Andrew Plotkin 4 | http://eblong.com/zarf/glk/ 5 | */ 6 | 7 | typedef struct window_graphics_struct { 8 | window_t *owner; 9 | 10 | data_specialspan_t **content; 11 | long numcontent; 12 | long contentsize; 13 | 14 | long updatemark; 15 | 16 | int graphwidth, graphheight; 17 | } window_graphics_t; 18 | 19 | extern window_graphics_t *win_graphics_create(window_t *win); 20 | extern void win_graphics_destroy(window_graphics_t *dwin); 21 | extern void win_graphics_rearrange(window_t *win, grect_t *box, data_metrics_t *metrics); 22 | extern void win_graphics_redraw(window_t *win); 23 | extern void win_graphics_putspecial(window_t *win, data_specialspan_t *span); 24 | extern data_content_t *win_graphics_update(window_t *win); 25 | extern void win_graphics_clear(window_t *win); 26 | extern void win_graphics_trim_buffer(window_t *win); 27 | -------------------------------------------------------------------------------- /rgwin_blank.c: -------------------------------------------------------------------------------- 1 | /* rgwin_blank.c: The blank window type 2 | for RemGlk, remote-procedure-call implementation of the Glk API. 3 | Designed by Andrew Plotkin 4 | http://eblong.com/zarf/glk/ 5 | */ 6 | 7 | #include 8 | #include 9 | #include "glk.h" 10 | #include "remglk.h" 11 | #include "rgwin_blank.h" 12 | 13 | /* This code is just as simple as you think. A blank window is filled with 14 | ':' characters on the screen, except for the corners, which are marked 15 | with slashes just so you can see where they are. */ 16 | 17 | window_blank_t *win_blank_create(window_t *win) 18 | { 19 | window_blank_t *dwin = (window_blank_t *)malloc(sizeof(window_blank_t)); 20 | dwin->owner = win; 21 | 22 | return dwin; 23 | } 24 | 25 | void win_blank_destroy(window_blank_t *dwin) 26 | { 27 | dwin->owner = NULL; 28 | free(dwin); 29 | } 30 | 31 | void win_blank_rearrange(window_t *win, grect_t *box, data_metrics_t *metrics) 32 | { 33 | window_blank_t *dwin = win->data; 34 | dwin->owner->bbox = *box; 35 | } 36 | 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2012-2025, Andrew Plotkin 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /gi_debug.c: -------------------------------------------------------------------------------- 1 | /* gi_debug.c: Debug feature layer for Glk API. 2 | gi_debug version 0.9.5. 3 | Designed by Andrew Plotkin 4 | http://eblong.com/zarf/glk/ 5 | 6 | This file is copyright 2014-7 by Andrew Plotkin. It is 7 | distributed under the MIT license; see the "LICENSE" file. 8 | */ 9 | 10 | #include "glk.h" 11 | #include "gi_debug.h" 12 | 13 | #ifndef NULL 14 | #define NULL 0 15 | #endif 16 | 17 | static gidebug_cmd_handler debug_cmd_handler = NULL; 18 | static gidebug_cycle_handler debug_cycle_handler = NULL; 19 | 20 | void gidebug_debugging_available(gidebug_cmd_handler cmdhandler, gidebug_cycle_handler cyclehandler) 21 | { 22 | debug_cmd_handler = cmdhandler; 23 | debug_cycle_handler = cyclehandler; 24 | } 25 | 26 | int gidebug_debugging_is_available() 27 | { 28 | return (debug_cmd_handler != NULL); 29 | } 30 | 31 | void gidebug_announce_cycle(gidebug_cycle cycle) 32 | { 33 | if (debug_cycle_handler) 34 | debug_cycle_handler(cycle); 35 | } 36 | 37 | int gidebug_perform_command(char *cmd) 38 | { 39 | if (!gidebug_debugging_is_available()) { 40 | #if GIDEBUG_LIBRARY_SUPPORT 41 | gidebug_output("The interpreter does not have a debug feature."); 42 | #endif /* GIDEBUG_LIBRARY_SUPPORT */ 43 | return 1; 44 | } 45 | 46 | return debug_cmd_handler(cmd); 47 | } 48 | 49 | -------------------------------------------------------------------------------- /rgwin_pair.h: -------------------------------------------------------------------------------- 1 | /* rgwin_pair.h: The pair window header 2 | for RemGlk, remote-procedure-call implementation of the Glk API. 3 | Designed by Andrew Plotkin 4 | http://eblong.com/zarf/glk/ 5 | */ 6 | 7 | typedef struct window_pair_struct { 8 | window_t *owner; 9 | 10 | window_t *child1, *child2; 11 | int splitpos; /* The split center. To be picky, this is the position 12 | of the top of the border, or the top of the bottom window if the 13 | border is zero-width. (If vertical is true, rotate this comment 14 | 90 degrees.) */ 15 | int splitwidth; /* The width of the border. */ 16 | 17 | /* split info... */ 18 | glui32 dir; /* winmethod_Left, Right, Above, or Below */ 19 | int vertical, backward, hasborder; /* flags */ 20 | glui32 division; /* winmethod_Fixed or winmethod_Proportional */ 21 | window_t *key; /* NULL or a leaf-descendant (not a Pair) */ 22 | int keydamage; /* used as scratch space in window closing */ 23 | glui32 size; /* size value */ 24 | 25 | } window_pair_t; 26 | 27 | extern window_pair_t *win_pair_create(window_t *win, glui32 method, 28 | window_t *key, glui32 size); 29 | extern void win_pair_destroy(window_pair_t *dwin); 30 | extern void win_pair_rearrange(window_t *win, grect_t *box, data_metrics_t *metrics); 31 | extern void win_pair_redraw(window_t *win); 32 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Unix Makefile for RemGlk library 2 | 3 | # This generates two files. One, of course, is libremglk.a -- the library 4 | # itself. The other is Make.remglk; this is a snippet of Makefile code 5 | # which locates the remglk library and associated libraries. 6 | # 7 | # When you install remglk, you must put libremglk.a in the lib directory, 8 | # and glk.h, glkstart.h, and Make.remglk in the include directory. 9 | 10 | # Pick a C compiler. 11 | #CC = cc 12 | #CC = gcc 13 | 14 | OPTIONS = -g -Wall -Wno-unused 15 | 16 | CFLAGS = $(OPTIONS) $(INCLUDEDIRS) 17 | 18 | GLKLIB = libremglk.a 19 | 20 | REMGLK_OBJS = \ 21 | main.o rgevent.o rgfref.o rggestal.o \ 22 | rgdata.o rgmisc.o rgauto.o rgstream.o rgstyle.o \ 23 | rgwin_blank.o rgwin_buf.o rgwin_grid.o rgwin_pair.o rgwin_graph.o \ 24 | rgwindow.o rgschan.o rgblorb.o \ 25 | cgunicod.o cgdate.o gi_dispa.o gi_debug.o gi_blorb.o 26 | 27 | REMGLK_HEADERS = \ 28 | remglk.h rgdata.h rgdata_int.h rgwin_blank.h rgwin_buf.h \ 29 | rgwin_grid.h rgwin_graph.h rgwin_pair.h gi_debug.h gi_dispa.h 30 | 31 | all: $(GLKLIB) Make.remglk 32 | 33 | cgunicod.o: cgunigen.c 34 | 35 | $(GLKLIB): $(REMGLK_OBJS) 36 | ar r $(GLKLIB) $(REMGLK_OBJS) 37 | ranlib $(GLKLIB) 38 | 39 | Make.remglk: 40 | echo LINKLIBS = $(LIBDIRS) $(LIBS) > Make.remglk 41 | echo GLKLIB = -lremglk >> Make.remglk 42 | 43 | $(REMGLK_OBJS): glk.h $(REMGLK_HEADERS) 44 | 45 | clean: 46 | rm -f *~ *.o $(GLKLIB) Make.remglk 47 | -------------------------------------------------------------------------------- /README.txt: -------------------------------------------------------------------------------- 1 | RemGlk: remote-procedure-call implementation of the Glk IF API 2 | 3 | RemGlk Library: version 0.3.2. 4 | Designed by Andrew Plotkin 5 | 6 | 7 | This is source code for an implementation of the Glk library which 8 | supports structured input and output. 9 | 10 | RemGlk does not provide a user interface. Instead, it wraps up the 11 | application's output as a JSON data structure and sends it to stdout. 12 | It then waits for input to arrive from stdin; the input data must also 13 | be encoded as JSON. 14 | 15 | RemGlk is therefore like CheapGlk, in that it works entirely through 16 | input and output streams, and can easily be attached to a bot or web 17 | service. However, unlike CheapGlk, RemGlk supports multiple Glk 18 | windows and most Glk I/O features. Whatever it's attached to just has 19 | to decode the structured output and display it appropriately. 20 | 21 | 22 | * Permissions 23 | 24 | The RemGlk library is copyright 2012-2025 by Andrew Plotkin. The 25 | GiDispa and GiBlorb libraries, as well as the glk.h header file, are 26 | copyright 1998-2025 by Andrew Plotkin. The GiDebug library is copyright 27 | 2014-2022 by Andrew Plotkin. All are distributed under the MIT license; 28 | see the "LICENSE" file. 29 | 30 | The RemGlk documentation is licensed under a Creative Commons 31 | Attribution-Noncommercial-ShareAlike 4.0 International License. 32 | See 33 | 34 | -------------------------------------------------------------------------------- /rgstyle.c: -------------------------------------------------------------------------------- 1 | /* rgstyle.c: Style formatting hints. 2 | for RemGlk, remote-procedure-call implementation of the Glk API. 3 | Designed by Andrew Plotkin 4 | http://eblong.com/zarf/glk/ 5 | */ 6 | 7 | #include 8 | #include "glk.h" 9 | #include "remglk.h" 10 | #include "rgdata.h" 11 | #include "rgwin_grid.h" 12 | #include "rgwin_buf.h" 13 | 14 | /* This version of the library doesn't accept style hints. */ 15 | 16 | void glk_stylehint_set(glui32 wintype, glui32 styl, glui32 hint, 17 | glsi32 val) 18 | { 19 | } 20 | 21 | void glk_stylehint_clear(glui32 wintype, glui32 styl, glui32 hint) 22 | { 23 | } 24 | 25 | glui32 glk_style_distinguish(window_t *win, glui32 styl1, glui32 styl2) 26 | { 27 | if (!win) { 28 | gli_strict_warning("style_distinguish: invalid ref"); 29 | return FALSE; 30 | } 31 | 32 | if (styl1 >= style_NUMSTYLES || styl2 >= style_NUMSTYLES) 33 | return FALSE; 34 | 35 | /* ### */ 36 | 37 | return FALSE; 38 | } 39 | 40 | glui32 glk_style_measure(window_t *win, glui32 styl, glui32 hint, 41 | glui32 *result) 42 | { 43 | glui32 dummy; 44 | 45 | if (!win) { 46 | gli_strict_warning("style_measure: invalid ref"); 47 | return FALSE; 48 | } 49 | 50 | if (styl >= style_NUMSTYLES || hint >= stylehint_NUMHINTS) 51 | return FALSE; 52 | 53 | if (!result) 54 | result = &dummy; 55 | 56 | /* ### */ 57 | 58 | return FALSE; 59 | } 60 | -------------------------------------------------------------------------------- /rgdata_int.h: -------------------------------------------------------------------------------- 1 | /* rgdata_int.h: JSON data structure header with serialization internals 2 | for RemGlk, remote-procedure-call implementation of the Glk API. 3 | Designed by Andrew Plotkin 4 | http://eblong.com/zarf/glk/ 5 | */ 6 | 7 | #include 8 | #include "glkstart.h" 9 | 10 | struct glkunix_serialize_context_struct { 11 | FILE *file; 12 | glui32 count; 13 | }; 14 | 15 | struct glkunix_unserialize_context_struct { 16 | data_raw_t *dat; 17 | struct glkunix_unserialize_context_struct *subctx; 18 | }; 19 | 20 | struct glkunix_library_state_struct { 21 | glui32 generation; 22 | data_metrics_t *metrics; 23 | data_supportcaps_t *supportcaps; 24 | 25 | window_t **windowlist; 26 | int windowcount; 27 | stream_t **streamlist; 28 | int streamcount; 29 | fileref_t **filereflist; 30 | int filerefcount; 31 | 32 | glui32 timerinterval; 33 | 34 | window_t *rootwin; 35 | stream_t *currentstr; 36 | }; 37 | 38 | extern glkunix_library_state_t glkunix_library_state_alloc(void); 39 | extern void glkunix_library_state_free(glkunix_library_state_t state); 40 | 41 | /* Some serialize/unserialize calls that are used by the library but not exported to game/interpreter code. */ 42 | 43 | extern void glkunix_serialize_object_root(FILE *file, struct glkunix_serialize_context_struct *ctx, glkunix_serialize_object_f func, void *rock); 44 | 45 | extern int glkunix_unserialize_int(glkunix_unserialize_context_t, char *, int *); 46 | extern int glkunix_unserialize_long(glkunix_unserialize_context_t, char *, long *); 47 | extern int glkunix_unserialize_real(glkunix_unserialize_context_t, char *, double *); 48 | extern int glkunix_unserialize_latin1_string(glkunix_unserialize_context_t, char *, char **); 49 | extern int glkunix_unserialize_len_bytes(glkunix_unserialize_context_t, char *, unsigned char **, long *); 50 | extern int glkunix_unserialize_len_unicode(glkunix_unserialize_context_t, char *, glui32 **, long *); 51 | 52 | extern int glkunix_unserialize_uint32_list_entry(glkunix_unserialize_context_t, int, glui32 *); 53 | 54 | extern int glkunix_unserialize_object_root(FILE *file, struct glkunix_unserialize_context_struct *ctx); 55 | extern void glkunix_unserialize_object_root_finalize(struct glkunix_unserialize_context_struct *ctx); 56 | -------------------------------------------------------------------------------- /rgwin_grid.h: -------------------------------------------------------------------------------- 1 | /* rgwin_grid.h: The grid window header 2 | for RemGlk, remote-procedure-call implementation of the Glk API. 3 | Designed by Andrew Plotkin 4 | http://eblong.com/zarf/glk/ 5 | */ 6 | 7 | /* One line of the window. */ 8 | typedef struct tgline_struct { 9 | int allocsize; /* this is the allocated size; only width is valid */ 10 | glui32 *chars; 11 | short *styles; 12 | glui32 *links; 13 | int dirty; 14 | } tgline_t; 15 | 16 | typedef struct window_textgrid_struct { 17 | window_t *owner; 18 | 19 | int width, height; 20 | tgline_t *lines; 21 | int linessize; /* this is the allocated size of the lines array; 22 | only the first height entries are valid. */ 23 | 24 | int curx, cury; /* the window cursor position */ 25 | 26 | int alldirty; /* all lines should be considered dirty */ 27 | 28 | /* The following are meaningful only for the current line input request. */ 29 | /* Note that inbuf points to memory outside the library. Usually it's owned by the dispatch layer. */ 30 | void *inbuf; /* char* or glui32*, depending on inunicode. */ 31 | glui32 incurpos; 32 | int inunicode; 33 | int inecho; 34 | glui32 intermkeys; 35 | int inoriglen, inmax; 36 | glui32 origstyle; 37 | gidispatch_rock_t inarrayrock; 38 | } window_textgrid_t; 39 | 40 | extern window_textgrid_t *win_textgrid_create(window_t *win); 41 | extern void win_textgrid_destroy(window_textgrid_t *dwin); 42 | extern void win_textgrid_alloc_lines(window_textgrid_t *dwin, int beg, int end, int linewid); 43 | extern void win_textgrid_rearrange(window_t *win, grect_t *box, data_metrics_t *metrics); 44 | extern void win_textgrid_redraw(window_t *win); 45 | extern data_content_t *win_textgrid_update(window_t *win); 46 | extern void win_textgrid_putchar(window_t *win, glui32 ch); 47 | extern void win_textgrid_clear(window_t *win); 48 | extern void win_textgrid_move_cursor(window_t *win, int xpos, int ypos); 49 | extern void win_textgrid_init_line(window_t *win, void *buf, int unicode, int maxlen, int initlen); 50 | extern void win_textgrid_prepare_input(window_t *win, glui32 *buf, glui32 len); 51 | extern void win_textgrid_accept_line(window_t *win); 52 | extern void win_textgrid_cancel_line(window_t *win, event_t *ev); 53 | -------------------------------------------------------------------------------- /rgwin_buf.h: -------------------------------------------------------------------------------- 1 | /* rgwin_buf.h: The buffer window header 2 | for RemGlk, remote-procedure-call implementation of the Glk API. 3 | Designed by Andrew Plotkin 4 | http://eblong.com/zarf/glk/ 5 | */ 6 | 7 | /* One style/link run */ 8 | typedef struct tbrun_struct { 9 | short style; 10 | glui32 hyperlink; 11 | long pos; 12 | long specialnum; 13 | } tbrun_t; 14 | 15 | typedef struct window_textbuffer_struct { 16 | window_t *owner; 17 | 18 | glui32 *chars; 19 | long numchars; 20 | long charssize; 21 | 22 | data_specialspan_t **specials; 23 | long numspecials; 24 | long specialssize; 25 | 26 | int width, height; 27 | 28 | long updatemark; 29 | int startclear; 30 | 31 | tbrun_t *runs; /* There is always at least one run. */ 32 | long numruns; 33 | long runssize; 34 | 35 | /* The following are meaningful only for the current line input request. */ 36 | /* Note that inbuf points to memory outside the library. Usually it's owned by the dispatch layer. */ 37 | void *inbuf; /* char* or glui32*, depending on inunicode. */ 38 | glui32 incurpos; 39 | int inunicode; 40 | int inecho; 41 | glui32 intermkeys; 42 | int inmax; 43 | glui32 origstyle; 44 | glui32 orighyperlink; 45 | gidispatch_rock_t inarrayrock; 46 | } window_textbuffer_t; 47 | 48 | extern window_textbuffer_t *win_textbuffer_create(window_t *win); 49 | extern void win_textbuffer_destroy(window_textbuffer_t *dwin); 50 | extern void win_textbuffer_rearrange(window_t *win, grect_t *box, data_metrics_t *metrics); 51 | extern void win_textbuffer_redraw(window_t *win); 52 | extern data_content_t *win_textbuffer_update(window_t *win); 53 | extern void win_textbuffer_putchar(window_t *win, glui32 ch); 54 | extern void win_textbuffer_putspecial(window_t *win, data_specialspan_t *special); 55 | extern void win_textbuffer_clear(window_t *win); 56 | extern void win_textbuffer_trim_buffer(window_t *win); 57 | extern void win_textbuffer_set_paging(window_t *win, int forcetoend); 58 | extern void win_textbuffer_init_line(window_t *win, void *buf, int unicode, int maxlen, int initlen); 59 | extern void win_textbuffer_accept_line(window_t *win); 60 | extern void win_textbuffer_prepare_input(window_t *win, glui32 *buf, glui32 len); 61 | extern void win_textbuffer_accept_line(window_t *win); 62 | extern void win_textbuffer_cancel_line(window_t *win, event_t *ev); 63 | 64 | -------------------------------------------------------------------------------- /rgschan.c: -------------------------------------------------------------------------------- 1 | /* rgschan.c: Sound channel objects 2 | for RemGlk, remote-procedure-call implementation of the Glk API. 3 | Designed by Andrew Plotkin 4 | http://eblong.com/zarf/glk/ 5 | */ 6 | 7 | #include 8 | #include "glk.h" 9 | #include "remglk.h" 10 | 11 | /* The whole sound-channel situation is very simple for us; 12 | we don't support it. */ 13 | 14 | #ifdef GLK_MODULE_SOUND 15 | 16 | schanid_t glk_schannel_create(glui32 rock) 17 | { 18 | return NULL; 19 | } 20 | 21 | void glk_schannel_destroy(schanid_t chan) 22 | { 23 | } 24 | 25 | schanid_t glk_schannel_iterate(schanid_t chan, glui32 *rockptr) 26 | { 27 | if (rockptr) 28 | *rockptr = 0; 29 | return NULL; 30 | } 31 | 32 | glui32 glk_schannel_get_rock(schanid_t chan) 33 | { 34 | gli_strict_warning("schannel_get_rock: invalid id."); 35 | return 0; 36 | } 37 | 38 | glui32 glk_schannel_play(schanid_t chan, glui32 snd) 39 | { 40 | gli_strict_warning("schannel_play: invalid id."); 41 | return 0; 42 | } 43 | 44 | glui32 glk_schannel_play_ext(schanid_t chan, glui32 snd, glui32 repeats, 45 | glui32 notify) 46 | { 47 | gli_strict_warning("schannel_play_ext: invalid id."); 48 | return 0; 49 | } 50 | 51 | void glk_schannel_stop(schanid_t chan) 52 | { 53 | gli_strict_warning("schannel_stop: invalid id."); 54 | } 55 | 56 | void glk_schannel_set_volume(schanid_t chan, glui32 vol) 57 | { 58 | gli_strict_warning("schannel_set_volume: invalid id."); 59 | } 60 | 61 | void glk_sound_load_hint(glui32 snd, glui32 flag) 62 | { 63 | gli_strict_warning("schannel_sound_load_hint: invalid id."); 64 | } 65 | 66 | #ifdef GLK_MODULE_SOUND2 67 | 68 | schanid_t glk_schannel_create_ext(glui32 rock, glui32 volume) 69 | { 70 | return NULL; 71 | } 72 | 73 | glui32 glk_schannel_play_multi(schanid_t *chanarray, glui32 chancount, 74 | glui32 *sndarray, glui32 soundcount, glui32 notify) 75 | { 76 | if (chancount != soundcount) 77 | gli_strict_warning("schannel_play_multi: channel count does not match sound count."); 78 | return 0; 79 | } 80 | 81 | void glk_schannel_pause(schanid_t chan) 82 | { 83 | gli_strict_warning("schannel_pause: invalid id."); 84 | } 85 | 86 | void glk_schannel_unpause(schanid_t chan) 87 | { 88 | gli_strict_warning("schannel_unpause: invalid id."); 89 | } 90 | 91 | void glk_schannel_set_volume_ext(schanid_t chan, glui32 vol, 92 | glui32 duration, glui32 notify) 93 | { 94 | gli_strict_warning("schannel_set_volume_ext: invalid id."); 95 | } 96 | 97 | #endif /* GLK_MODULE_SOUND2 */ 98 | 99 | #endif /* GLK_MODULE_SOUND */ 100 | -------------------------------------------------------------------------------- /gi_blorb.h: -------------------------------------------------------------------------------- 1 | #ifndef _GI_BLORB_H 2 | #define _GI_BLORB_H 3 | 4 | /* gi_blorb.h: Blorb library layer for Glk API. 5 | gi_blorb version 1.6.2. 6 | Designed by Andrew Plotkin 7 | http://eblong.com/zarf/glk/ 8 | 9 | This file is copyright 1998-2017 by Andrew Plotkin. It is 10 | distributed under the MIT license; see the "LICENSE" file. 11 | */ 12 | 13 | /* Error type and error codes */ 14 | typedef glui32 giblorb_err_t; 15 | #define giblorb_err_None (0) 16 | #define giblorb_err_CompileTime (1) 17 | #define giblorb_err_Alloc (2) 18 | #define giblorb_err_Read (3) 19 | #define giblorb_err_NotAMap (4) 20 | #define giblorb_err_Format (5) 21 | #define giblorb_err_NotFound (6) 22 | 23 | /* Methods for loading a chunk */ 24 | #define giblorb_method_DontLoad (0) 25 | #define giblorb_method_Memory (1) 26 | #define giblorb_method_FilePos (2) 27 | 28 | /* Four-byte constants */ 29 | 30 | #define giblorb_make_id(c1, c2, c3, c4) \ 31 | (((c1) << 24) | ((c2) << 16) | ((c3) << 8) | (c4)) 32 | 33 | #define giblorb_ID_Exec (giblorb_make_id('E', 'x', 'e', 'c')) 34 | #define giblorb_ID_Snd (giblorb_make_id('S', 'n', 'd', ' ')) 35 | #define giblorb_ID_Pict (giblorb_make_id('P', 'i', 'c', 't')) 36 | #define giblorb_ID_Data (giblorb_make_id('D', 'a', 't', 'a')) 37 | #define giblorb_ID_Copyright (giblorb_make_id('(', 'c', ')', ' ')) 38 | #define giblorb_ID_AUTH (giblorb_make_id('A', 'U', 'T', 'H')) 39 | #define giblorb_ID_ANNO (giblorb_make_id('A', 'N', 'N', 'O')) 40 | #define giblorb_ID_TEXT (giblorb_make_id('T', 'E', 'X', 'T')) 41 | #define giblorb_ID_BINA (giblorb_make_id('B', 'I', 'N', 'A')) 42 | #define giblorb_ID_JPEG (giblorb_make_id('J', 'P', 'E', 'G')) 43 | #define giblorb_ID_PNG (giblorb_make_id('P', 'N', 'G', ' ')) 44 | 45 | /* giblorb_map_t: Holds the complete description of an open Blorb 46 | file. This type is opaque for normal interpreter use. */ 47 | typedef struct giblorb_map_struct giblorb_map_t; 48 | 49 | /* giblorb_result_t: Result when you try to load a chunk. */ 50 | typedef struct giblorb_result_struct { 51 | glui32 chunknum; /* The chunk number (for use in 52 | giblorb_unload_chunk(), etc.) */ 53 | union { 54 | void *ptr; /* A pointer to the data (if you used 55 | giblorb_method_Memory) */ 56 | glui32 startpos; /* The position in the file (if you 57 | used giblorb_method_FilePos) */ 58 | } data; 59 | glui32 length; /* The length of the data */ 60 | glui32 chunktype; /* The type of the chunk. */ 61 | } giblorb_result_t; 62 | 63 | typedef struct giblorb_image_info_struct { 64 | glui32 chunktype; 65 | glui32 width; 66 | glui32 height; 67 | char *alttext; 68 | } giblorb_image_info_t; 69 | 70 | extern giblorb_err_t giblorb_create_map(strid_t file, 71 | giblorb_map_t **newmap); 72 | extern giblorb_err_t giblorb_destroy_map(giblorb_map_t *map); 73 | 74 | extern giblorb_err_t giblorb_load_chunk_by_type(giblorb_map_t *map, 75 | glui32 method, giblorb_result_t *res, glui32 chunktype, 76 | glui32 count); 77 | extern giblorb_err_t giblorb_load_chunk_by_number(giblorb_map_t *map, 78 | glui32 method, giblorb_result_t *res, glui32 chunknum); 79 | extern giblorb_err_t giblorb_unload_chunk(giblorb_map_t *map, 80 | glui32 chunknum); 81 | 82 | extern giblorb_err_t giblorb_load_resource(giblorb_map_t *map, 83 | glui32 method, giblorb_result_t *res, glui32 usage, 84 | glui32 resnum); 85 | extern giblorb_err_t giblorb_count_resources(giblorb_map_t *map, 86 | glui32 usage, glui32 *num, glui32 *min, glui32 *max); 87 | 88 | extern giblorb_err_t giblorb_load_image_info(giblorb_map_t *map, 89 | glui32 resnum, giblorb_image_info_t *res); 90 | 91 | /* The following functions are part of the Glk library itself, not 92 | the Blorb layer (whose code is in gi_blorb.c). These functions 93 | are necessarily implemented in platform-dependent code. 94 | */ 95 | extern giblorb_err_t giblorb_set_resource_map(strid_t file); 96 | extern giblorb_err_t giblorb_unset_resource_map(void); 97 | extern giblorb_map_t *giblorb_get_resource_map(void); 98 | 99 | #endif /* _GI_BLORB_H */ 100 | -------------------------------------------------------------------------------- /gi_dispa.h: -------------------------------------------------------------------------------- 1 | #ifndef _GI_DISPA_H 2 | #define _GI_DISPA_H 3 | 4 | /* gi_dispa.h: Header file for dispatch layer of Glk API, version 0.7.6. 5 | Designed by Andrew Plotkin 6 | http://eblong.com/zarf/glk/index.html 7 | 8 | This file is copyright 1998-2025 by Andrew Plotkin. It is 9 | distributed under the MIT license; see the "LICENSE" file. 10 | */ 11 | 12 | /* These constants define the classes of opaque objects. It's a bit ugly 13 | to put them in this header file, since more classes may be added in 14 | the future. But if you find yourself stuck with an obsolete version 15 | of this file, adding new class definitions will be easy enough -- 16 | they will be numbered sequentially, and the numeric constants can be 17 | found in the Glk specification. */ 18 | #define gidisp_Class_Window (0) 19 | #define gidisp_Class_Stream (1) 20 | #define gidisp_Class_Fileref (2) 21 | #define gidisp_Class_Schannel (3) 22 | 23 | typedef union gluniversal_union { 24 | glui32 uint; /* Iu */ 25 | glsi32 sint; /* Is */ 26 | void *opaqueref; /* Qa, Qb, Qc... */ 27 | unsigned char uch; /* Cu */ 28 | signed char sch; /* Cs */ 29 | char ch; /* Cn */ 30 | char *charstr; /* S */ 31 | glui32 *unicharstr; /* U */ 32 | void *array; /* all # arguments */ 33 | glui32 ptrflag; /* [ ... ] or *? */ 34 | } gluniversal_t; 35 | 36 | /* Some well-known structures: 37 | event_t : [4IuQaIuIu] 38 | stream_result_t : [2IuIu] 39 | */ 40 | 41 | typedef struct gidispatch_function_struct { 42 | glui32 id; 43 | void *fnptr; 44 | char *name; 45 | } gidispatch_function_t; 46 | 47 | typedef struct gidispatch_intconst_struct { 48 | char *name; 49 | glui32 val; 50 | } gidispatch_intconst_t; 51 | 52 | typedef union glk_objrock_union { 53 | glui32 num; 54 | void *ptr; 55 | } gidispatch_rock_t; 56 | 57 | /* The following functions are part of the Glk library itself, not the dispatch 58 | layer (whose code is in gi_dispa.c). These functions are necessarily 59 | implemented in platform-dependent code. 60 | */ 61 | extern void gidispatch_set_object_registry( 62 | gidispatch_rock_t (*regi)(void *obj, glui32 objclass), 63 | void (*unregi)(void *obj, glui32 objclass, gidispatch_rock_t objrock)); 64 | extern gidispatch_rock_t gidispatch_get_objrock(void *obj, glui32 objclass); 65 | extern void gidispatch_set_retained_registry( 66 | gidispatch_rock_t (*regi)(void *array, glui32 len, char *typecode), 67 | void (*unregi)(void *array, glui32 len, char *typecode, 68 | gidispatch_rock_t objrock)); 69 | 70 | /* This function is also part of the Glk library, but it only exists 71 | on libraries that support autorestore. (Only iosglk, currently.) 72 | Only call this if GIDISPATCH_AUTORESTORE_REGISTRY is defined. 73 | */ 74 | #define GIDISPATCH_AUTORESTORE_REGISTRY 75 | extern void gidispatch_set_autorestore_registry( 76 | long (*locatearr)(void *array, glui32 len, char *typecode, 77 | gidispatch_rock_t objrock, int *elemsizeref), 78 | gidispatch_rock_t (*restorearr)(long bufkey, glui32 len, 79 | char *typecode, void **arrayref)); 80 | 81 | /* The following functions make up the Glk dispatch layer. Although they are 82 | distributed as part of each Glk library (linked into the library file), 83 | their code is in gi_dispa.c, which is platform-independent and identical 84 | in every Glk library. 85 | */ 86 | extern void gidispatch_call(glui32 funcnum, glui32 numargs, 87 | gluniversal_t *arglist); 88 | extern char *gidispatch_prototype(glui32 funcnum); 89 | extern glui32 gidispatch_count_classes(void); 90 | extern gidispatch_intconst_t *gidispatch_get_class(glui32 index); 91 | extern glui32 gidispatch_count_intconst(void); 92 | extern gidispatch_intconst_t *gidispatch_get_intconst(glui32 index); 93 | extern glui32 gidispatch_count_functions(void); 94 | extern gidispatch_function_t *gidispatch_get_function(glui32 index); 95 | extern gidispatch_function_t *gidispatch_get_function_by_id(glui32 id); 96 | 97 | #define GI_DISPA_GAME_ID_AVAILABLE 98 | /* These function is not part of Glk dispatching per se; they allow the 99 | game to provide an identifier string for the Glk library to use. 100 | The functions themselves are in gi_dispa.c. 101 | 102 | The game should test ifdef GI_DISPA_GAME_ID_AVAILABLE to ensure that 103 | these functions exist. (They are a late addition to gi_dispa.c, so 104 | older Glk library distributions will lack them.) 105 | */ 106 | #ifdef GI_DISPA_GAME_ID_AVAILABLE 107 | extern void gidispatch_set_game_id_hook(char *(*hook)(void)); 108 | extern char *gidispatch_get_game_id(void); 109 | #endif /* GI_DISPA_GAME_ID_AVAILABLE */ 110 | 111 | #endif /* _GI_DISPA_H */ 112 | -------------------------------------------------------------------------------- /rggestal.c: -------------------------------------------------------------------------------- 1 | /* rggestal.c: The Gestalt system 2 | for RemGlk, remote-procedure-call implementation of the Glk API. 3 | Designed by Andrew Plotkin 4 | http://eblong.com/zarf/glk/ 5 | */ 6 | 7 | #include 8 | #include 9 | #include 10 | #include "glk.h" 11 | #include "remglk.h" 12 | #include "rgdata.h" 13 | 14 | glui32 glk_gestalt(glui32 id, glui32 val) 15 | { 16 | return glk_gestalt_ext(id, val, NULL, 0); 17 | } 18 | 19 | glui32 glk_gestalt_ext(glui32 id, glui32 val, glui32 *arr, glui32 arrlen) 20 | { 21 | switch (id) { 22 | 23 | case gestalt_Version: 24 | /* This implements Glk spec version 0.7.6. */ 25 | return 0x00000706; 26 | 27 | case gestalt_LineInput: 28 | if (val >= 32 && val < 127) 29 | return TRUE; 30 | else 31 | return FALSE; 32 | 33 | case gestalt_CharInput: 34 | if (val >= 32 && val < 127) 35 | return TRUE; 36 | else if (val == keycode_Return) 37 | return TRUE; 38 | else { 39 | /* We're doing UTF-8 input, so we can input any Unicode 40 | character. Except control characters. */ 41 | return (val >= 160 && val < 0x200000); 42 | } 43 | 44 | case gestalt_CharOutput: 45 | if (val >= 32 && val < 127) { 46 | if (arr && arrlen >= 1) 47 | arr[0] = 1; 48 | return gestalt_CharOutput_ExactPrint; 49 | } 50 | else { 51 | /* cheaply, we don't do any translation of printed 52 | characters, so the output is always one character 53 | even if it's wrong. */ 54 | if (arr && arrlen >= 1) 55 | arr[0] = 1; 56 | /* We're doing UTF-8 output, so we can print any Unicode 57 | character. Except control characters. */ 58 | if (val >= 160 && val < 0x200000) 59 | return gestalt_CharOutput_ExactPrint; 60 | else 61 | return gestalt_CharOutput_CannotPrint; 62 | } 63 | 64 | case gestalt_MouseInput: 65 | return TRUE; 66 | 67 | case gestalt_Timer: 68 | return gli_supportcaps.timer; 69 | 70 | case gestalt_Graphics: 71 | case gestalt_GraphicsTransparency: 72 | return gli_supportcaps.graphics; 73 | 74 | case gestalt_GraphicsCharInput: 75 | return FALSE; 76 | 77 | case gestalt_DrawImage: 78 | if (gli_supportcaps.graphics) { 79 | if (val == wintype_TextBuffer) 80 | return TRUE; 81 | if (val == wintype_Graphics && gli_supportcaps.graphicswin) 82 | return TRUE; 83 | } 84 | return FALSE; 85 | 86 | case gestalt_DrawImageScale: 87 | if (gli_supportcaps.graphics && gli_supportcaps.graphicsext) { 88 | if (val == wintype_TextBuffer) 89 | return TRUE; 90 | if (val == wintype_Graphics && gli_supportcaps.graphicswin) 91 | return TRUE; 92 | } 93 | return FALSE; 94 | 95 | case gestalt_Unicode: 96 | return TRUE; 97 | 98 | case gestalt_UnicodeNorm: 99 | return TRUE; 100 | 101 | case gestalt_Sound: 102 | case gestalt_SoundVolume: 103 | case gestalt_SoundNotify: 104 | case gestalt_SoundMusic: 105 | return FALSE; 106 | 107 | case gestalt_Hyperlinks: 108 | case gestalt_HyperlinkInput: 109 | return gli_supportcaps.hyperlinks; 110 | 111 | case gestalt_LineInputEcho: 112 | return TRUE; 113 | 114 | case gestalt_LineTerminators: 115 | return FALSE; /* ### for now */ 116 | case gestalt_LineTerminatorKey: 117 | /* RemGlk never uses the escape or function keys for anything, 118 | so we'll allow them to be line terminators. */ 119 | if (val == keycode_Escape) 120 | return TRUE; 121 | if (val >= keycode_Func12 && val <= keycode_Func1) 122 | return TRUE; 123 | return FALSE; 124 | 125 | case gestalt_DateTime: 126 | return TRUE; 127 | 128 | case gestalt_ResourceStream: 129 | return TRUE; 130 | 131 | default: 132 | return 0; 133 | 134 | } 135 | } 136 | 137 | -------------------------------------------------------------------------------- /glkstart.h: -------------------------------------------------------------------------------- 1 | /* glkstart.h: Unix-specific header file for GlkTerm, CheapGlk, and XGlk 2 | (Unix implementations of the Glk API). 3 | Designed by Andrew Plotkin 4 | http://eblong.com/zarf/glk/ 5 | */ 6 | 7 | /* This header defines an interface that must be used by program linked 8 | with the various Unix Glk libraries -- at least, the three I wrote. 9 | (I encourage anyone writing a Unix Glk library to use this interface, 10 | but it's not part of the Glk spec.) 11 | 12 | Because Glk is *almost* perfectly portable, this interface *almost* 13 | doesn't have to exist. In practice, it's small. 14 | 15 | (Except for autosave. That makes everything complicated.) 16 | */ 17 | 18 | #ifndef GT_START_H 19 | #define GT_START_H 20 | 21 | #include /* for size_t */ 22 | 23 | /* We define our own TRUE and FALSE and NULL, because ANSI 24 | is a strange world. */ 25 | #ifndef TRUE 26 | #define TRUE 1 27 | #endif 28 | #ifndef FALSE 29 | #define FALSE 0 30 | #endif 31 | #ifndef NULL 32 | #define NULL 0 33 | #endif 34 | 35 | #define glkunix_arg_End (0) 36 | #define glkunix_arg_ValueFollows (1) 37 | #define glkunix_arg_NoValue (2) 38 | #define glkunix_arg_ValueCanFollow (3) 39 | #define glkunix_arg_NumberValue (4) 40 | 41 | typedef struct glkunix_argumentlist_struct { 42 | char *name; 43 | int argtype; 44 | char *desc; 45 | } glkunix_argumentlist_t; 46 | 47 | typedef struct glkunix_startup_struct { 48 | int argc; 49 | char **argv; 50 | } glkunix_startup_t; 51 | 52 | extern glkunix_argumentlist_t glkunix_arguments[]; 53 | 54 | /* defined in unixstrt.c */ 55 | extern int glkunix_startup_code(glkunix_startup_t *data); 56 | 57 | /* This library offers the glkunix_fileref_get_filename() API 58 | (used by some VMs originally built for GarGlk). */ 59 | #define GLKUNIX_FILEREF_GET_FILENAME (1) 60 | 61 | extern void glkunix_set_base_file(char *filename); 62 | extern strid_t glkunix_stream_open_pathname_gen(char *pathname, 63 | glui32 writemode, glui32 textmode, glui32 rock); 64 | extern strid_t glkunix_stream_open_pathname(char *pathname, glui32 textmode, 65 | glui32 rock); 66 | #ifdef GLKUNIX_FILEREF_GET_FILENAME 67 | extern const char *glkunix_fileref_get_filename(frefid_t fref); 68 | #endif /* GLKUNIX_FILEREF_GET_FILENAME */ 69 | 70 | typedef struct glkunix_serialize_context_struct *glkunix_serialize_context_t; 71 | typedef struct glkunix_unserialize_context_struct *glkunix_unserialize_context_t; 72 | typedef int (*glkunix_serialize_object_f)(glkunix_serialize_context_t, void *); 73 | typedef int (*glkunix_unserialize_object_f)(glkunix_unserialize_context_t, void *); 74 | 75 | extern void glkunix_serialize_uint32(glkunix_serialize_context_t, char *, glui32); 76 | extern void glkunix_serialize_object(glkunix_serialize_context_t, char *, glkunix_serialize_object_f, void *); 77 | extern void glkunix_serialize_object_list(glkunix_serialize_context_t, char *, glkunix_serialize_object_f, int, size_t, void *); 78 | 79 | extern int glkunix_unserialize_uint32(glkunix_unserialize_context_t, char *, glui32 *); 80 | extern int glkunix_unserialize_struct(glkunix_unserialize_context_t, char *, glkunix_unserialize_context_t *); 81 | extern int glkunix_unserialize_list(glkunix_unserialize_context_t, char *, glkunix_unserialize_context_t *, int *); 82 | extern int glkunix_unserialize_list_entry(glkunix_unserialize_context_t, int, glkunix_unserialize_context_t *); 83 | extern int glkunix_unserialize_object_list_entries(glkunix_unserialize_context_t, glkunix_unserialize_object_f, int, size_t, void *); 84 | 85 | /* This library offers the hooks necessary for an interpreter to 86 | implement autosave. */ 87 | #define GLKUNIX_AUTOSAVE_FEATURES (1) 88 | 89 | #ifdef GLKUNIX_AUTOSAVE_FEATURES 90 | 91 | /* glkunix_library_state_struct is defined in rgdata_int.h. */ 92 | typedef struct glkunix_library_state_struct *glkunix_library_state_t; 93 | /* glk_objrock_union is defined in gi_dispa.h. gidispatch_rock_t is another alias for this union; but I don't want this header to be dependent on that one, so we use glk_objrock_u below. */ 94 | typedef union glk_objrock_union glk_objrock_u; 95 | 96 | extern void glkunix_save_library_state(strid_t file, strid_t omitstream, glkunix_serialize_object_f extra_state_func, void *extra_state_rock); 97 | extern glkunix_library_state_t glkunix_load_library_state(strid_t file, glkunix_unserialize_object_f extra_state_func, void *extra_state_rock); 98 | extern glui32 glkunix_update_from_library_state(glkunix_library_state_t state); 99 | extern void glkunix_library_state_free(glkunix_library_state_t state); 100 | 101 | extern glui32 glkunix_get_last_event_type(void); 102 | extern glui32 glkunix_window_get_updatetag(winid_t win); 103 | extern winid_t glkunix_window_find_by_updatetag(glui32 tag); 104 | extern void glkunix_window_set_dispatch_rock(winid_t win, glk_objrock_u rock); 105 | extern glui32 glkunix_stream_get_updatetag(strid_t str); 106 | extern strid_t glkunix_stream_find_by_updatetag(glui32 tag); 107 | extern void glkunix_stream_set_dispatch_rock(strid_t str, glk_objrock_u rock); 108 | extern glui32 glkunix_fileref_get_updatetag(frefid_t fref); 109 | extern frefid_t glkunix_fileref_find_by_updatetag(glui32 tag); 110 | extern void glkunix_fileref_set_dispatch_rock(frefid_t fref, glk_objrock_u rock); 111 | 112 | #endif /* GLKUNIX_AUTOSAVE_FEATURES */ 113 | 114 | #endif /* GT_START_H */ 115 | 116 | -------------------------------------------------------------------------------- /rgwin_pair.c: -------------------------------------------------------------------------------- 1 | /* rgwin_pair.c: The pair window type 2 | for RemGlk, remote-procedure-call implementation of the Glk API. 3 | Designed by Andrew Plotkin 4 | http://eblong.com/zarf/glk/ 5 | */ 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | #include "glk.h" 12 | #include "remglk.h" 13 | #include "rgdata.h" 14 | #include "rgwin_pair.h" 15 | 16 | window_pair_t *win_pair_create(window_t *win, glui32 method, window_t *key, 17 | glui32 size) 18 | { 19 | window_pair_t *dwin = (window_pair_t *)malloc(sizeof(window_pair_t)); 20 | dwin->owner = win; 21 | 22 | dwin->dir = method & winmethod_DirMask; 23 | dwin->division = method & winmethod_DivisionMask; 24 | dwin->hasborder = ((method & winmethod_BorderMask) == winmethod_Border); 25 | dwin->key = key; 26 | dwin->keydamage = FALSE; 27 | dwin->size = size; 28 | 29 | dwin->vertical = (dwin->dir == winmethod_Left || dwin->dir == winmethod_Right); 30 | dwin->backward = (dwin->dir == winmethod_Left || dwin->dir == winmethod_Above); 31 | 32 | dwin->child1 = NULL; 33 | dwin->child2 = NULL; 34 | 35 | return dwin; 36 | } 37 | 38 | void win_pair_destroy(window_pair_t *dwin) 39 | { 40 | dwin->owner = NULL; 41 | /* We leave the children untouched, because gli_window_close takes care 42 | of that if it's desired. */ 43 | dwin->child1 = NULL; 44 | dwin->child2 = NULL; 45 | dwin->key = NULL; 46 | free(dwin); 47 | } 48 | 49 | void win_pair_rearrange(window_t *win, grect_t *box, data_metrics_t *metrics) 50 | { 51 | window_pair_t *dwin = win->data; 52 | grect_t box1, box2; 53 | int min, diff, split, splitwid, max; 54 | window_t *key; 55 | window_t *ch1, *ch2; 56 | 57 | win->bbox = *box; 58 | /*dwin->flat = FALSE;*/ 59 | 60 | if (dwin->vertical) { 61 | min = win->bbox.left; 62 | max = win->bbox.right; 63 | } 64 | else { 65 | min = win->bbox.top; 66 | max = win->bbox.bottom; 67 | } 68 | diff = max-min; 69 | 70 | /* We now figure split. */ 71 | if (dwin->hasborder) { 72 | if (dwin->vertical) 73 | splitwid = (int) ceil(metrics->inspacingx); 74 | else 75 | splitwid = (int) ceil(metrics->inspacingy); 76 | } 77 | else { 78 | splitwid = 0; 79 | } 80 | 81 | switch (dwin->division) { 82 | case winmethod_Proportional: 83 | split = (int) round(((double)diff * dwin->size) / 100.0); 84 | break; 85 | case winmethod_Fixed: 86 | key = dwin->key; 87 | if (!key) { 88 | split = 0; 89 | } 90 | else { 91 | switch (key->type) { 92 | case wintype_TextBuffer: 93 | if (dwin->vertical) 94 | split = (int) ceil(dwin->size * metrics->buffercharwidth + metrics->buffermarginx); 95 | else 96 | split = (int) ceil(dwin->size * metrics->buffercharheight + metrics->buffermarginy); 97 | break; 98 | case wintype_TextGrid: 99 | if (dwin->vertical) 100 | split = (int) ceil(dwin->size * metrics->gridcharwidth + metrics->gridmarginx); 101 | else 102 | split = (int) ceil(dwin->size * metrics->gridcharheight + metrics->gridmarginy); 103 | break; 104 | case wintype_Graphics: 105 | if (dwin->vertical) 106 | split = (int) ceil(dwin->size + metrics->graphicsmarginx); 107 | else 108 | split = (int) ceil(dwin->size + metrics->graphicsmarginy); 109 | break; 110 | default: 111 | split = 0; 112 | break; 113 | } 114 | } 115 | break; 116 | default: 117 | split = diff / 2; 118 | break; 119 | } 120 | 121 | if (!dwin->backward) { 122 | split = max-split-splitwid; 123 | } 124 | else { 125 | split = min+split; 126 | } 127 | 128 | if (min >= max) { 129 | split = min; 130 | } 131 | else { 132 | if (split < min) 133 | split = min; 134 | else if (split > max-splitwid) 135 | split = max-splitwid; 136 | } 137 | 138 | if (dwin->vertical) { 139 | dwin->splitpos = split; 140 | dwin->splitwidth = splitwid; 141 | box1.left = win->bbox.left; 142 | box1.right = dwin->splitpos; 143 | box2.left = box1.right + dwin->splitwidth; 144 | box2.right = win->bbox.right; 145 | box1.top = win->bbox.top; 146 | box1.bottom = win->bbox.bottom; 147 | box2.top = win->bbox.top; 148 | box2.bottom = win->bbox.bottom; 149 | if (!dwin->backward) { 150 | ch1 = dwin->child1; 151 | ch2 = dwin->child2; 152 | } 153 | else { 154 | ch1 = dwin->child2; 155 | ch2 = dwin->child1; 156 | } 157 | } 158 | else { 159 | dwin->splitpos = split; 160 | dwin->splitwidth = splitwid; 161 | box1.top = win->bbox.top; 162 | box1.bottom = dwin->splitpos; 163 | box2.top = box1.bottom + dwin->splitwidth; 164 | box2.bottom = win->bbox.bottom; 165 | box1.left = win->bbox.left; 166 | box1.right = win->bbox.right; 167 | box2.left = win->bbox.left; 168 | box2.right = win->bbox.right; 169 | if (!dwin->backward) { 170 | ch1 = dwin->child1; 171 | ch2 = dwin->child2; 172 | } 173 | else { 174 | ch1 = dwin->child2; 175 | ch2 = dwin->child1; 176 | } 177 | } 178 | 179 | gli_window_rearrange(ch1, &box1, metrics); 180 | gli_window_rearrange(ch2, &box2, metrics); 181 | } 182 | 183 | -------------------------------------------------------------------------------- /rgwin_graph.c: -------------------------------------------------------------------------------- 1 | /* rgwin_graph.c: The graphics window type 2 | for RemGlk, remote-procedure-call implementation of the Glk API. 3 | Designed by Andrew Plotkin 4 | http://eblong.com/zarf/glk/ 5 | */ 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include "glk.h" 13 | #include "remglk.h" 14 | #include "rgdata.h" 15 | #include "rgwin_graph.h" 16 | 17 | window_graphics_t *win_graphics_create(window_t *win) 18 | { 19 | window_graphics_t *dwin = (window_graphics_t *)malloc(sizeof(window_graphics_t)); 20 | dwin->owner = win; 21 | 22 | dwin->numcontent = 0; 23 | dwin->contentsize = 4; 24 | dwin->content = (data_specialspan_t **)malloc(dwin->contentsize * sizeof(data_specialspan_t *)); 25 | 26 | dwin->updatemark = 0; 27 | 28 | dwin->graphwidth = 0; 29 | dwin->graphheight = 0; 30 | 31 | return dwin; 32 | } 33 | 34 | void win_graphics_destroy(window_graphics_t *dwin) 35 | { 36 | dwin->owner = NULL; 37 | 38 | if (dwin->content) { 39 | long px; 40 | for (px=0; pxnumcontent; px++) 41 | data_specialspan_free(dwin->content[px]); 42 | free(dwin->content); 43 | dwin->content = NULL; 44 | } 45 | 46 | free(dwin); 47 | } 48 | 49 | void win_graphics_clear(window_t *win) 50 | { 51 | window_graphics_t *dwin = win->data; 52 | 53 | /* If the background color has been set, we must retain that entry. 54 | (The last setcolor, if there are several.) */ 55 | 56 | data_specialspan_t *setcolspan = NULL; 57 | int setcolmarked = FALSE; 58 | 59 | /* Discard all the content entries, except for the last setcolor. */ 60 | long px; 61 | for (px=0; pxnumcontent; px++) { 62 | data_specialspan_t *span = dwin->content[px]; 63 | dwin->content[px] = NULL; 64 | 65 | if (span->type == specialtype_SetColor) { 66 | if (setcolspan) 67 | data_specialspan_free(setcolspan); 68 | setcolspan = span; 69 | setcolmarked = (px >= dwin->updatemark); 70 | } 71 | else { 72 | data_specialspan_free(span); 73 | } 74 | } 75 | 76 | dwin->numcontent = 0; 77 | dwin->updatemark = 0; 78 | 79 | /* Put back the setcolor (if found). Note that contentsize is at 80 | least 4. */ 81 | if (setcolspan) { 82 | dwin->content[dwin->numcontent] = setcolspan; 83 | dwin->numcontent++; 84 | if (!setcolmarked) 85 | dwin->updatemark++; 86 | } 87 | 88 | /* Clear to background color. */ 89 | data_specialspan_t *fillspan = data_specialspan_alloc(specialtype_Fill); 90 | dwin->content[dwin->numcontent] = fillspan; 91 | dwin->numcontent++; 92 | } 93 | 94 | void win_graphics_rearrange(window_t *win, grect_t *box, data_metrics_t *metrics) 95 | { 96 | window_graphics_t *dwin = win->data; 97 | dwin->owner->bbox = *box; 98 | 99 | int width = box->right - box->left; 100 | int height = box->bottom - box->top; 101 | 102 | dwin->graphwidth = (int) floor(width - metrics->graphicsmarginx); 103 | if (dwin->graphwidth < 0) 104 | dwin->graphwidth = 0; 105 | dwin->graphheight = (int) floor(height - metrics->graphicsmarginy); 106 | if (dwin->graphheight < 0) 107 | dwin->graphheight = 0; 108 | } 109 | 110 | void win_graphics_putspecial(window_t *win, data_specialspan_t *span) 111 | { 112 | window_graphics_t *dwin = win->data; 113 | 114 | if (dwin->numcontent >= dwin->contentsize) { 115 | dwin->contentsize *= 2; 116 | dwin->content = (data_specialspan_t **)realloc(dwin->content, 117 | dwin->contentsize * sizeof(data_specialspan_t *)); 118 | } 119 | 120 | dwin->content[dwin->numcontent] = span; 121 | dwin->numcontent++; 122 | } 123 | 124 | data_content_t *win_graphics_update(window_t *win) 125 | { 126 | window_graphics_t *dwin = win->data; 127 | 128 | data_content_t *dat = NULL; 129 | 130 | if (dwin->numcontent > dwin->updatemark) { 131 | long px; 132 | dat = data_content_alloc(win->updatetag, win->type); 133 | data_line_t *line = data_line_alloc(); 134 | gen_list_append(&dat->lines, line); 135 | 136 | for (px=dwin->updatemark; pxnumcontent; px++) { 137 | data_specialspan_t *span = dwin->content[px]; 138 | data_line_add_specialspan(line, span); 139 | } 140 | 141 | dwin->updatemark = dwin->numcontent; 142 | } 143 | 144 | return dat; 145 | } 146 | 147 | void win_graphics_trim_buffer(window_t *win) 148 | { 149 | window_graphics_t *dwin = win->data; 150 | 151 | /* If a whole-window fill command has been sent, we're going to drop 152 | all commands before it. Except that we save the last setcolor 153 | of the trimmed range. */ 154 | 155 | long px; 156 | long lastfill = -1; 157 | for (px=0; pxupdatemark; px++) { 158 | data_specialspan_t *span = dwin->content[px]; 159 | if (span->type == specialtype_Fill && !span->hasdimensions) { 160 | lastfill = px; 161 | } 162 | } 163 | 164 | if (lastfill <= 0) { 165 | return; 166 | } 167 | 168 | data_specialspan_t *lastsetcol = NULL; 169 | for (px=0; pxcontent[px]; 171 | dwin->content[px] = NULL; 172 | if (span->type == specialtype_SetColor) { 173 | if (lastsetcol) 174 | data_specialspan_free(lastsetcol); 175 | lastsetcol = span; 176 | } 177 | else { 178 | data_specialspan_free(span); 179 | } 180 | } 181 | 182 | long delta; 183 | if (!lastsetcol) { 184 | delta = lastfill; 185 | if (lastfill > 0 && lastfill < dwin->numcontent) 186 | memmove(dwin->content, &dwin->content[lastfill], 187 | (dwin->numcontent-lastfill) * sizeof(data_specialspan_t *)); 188 | } 189 | else { 190 | delta = lastfill-1; 191 | dwin->content[0] = lastsetcol; 192 | if (lastfill > 1 && lastfill < dwin->numcontent) 193 | memmove(&dwin->content[1], &dwin->content[lastfill], 194 | (dwin->numcontent-lastfill) * sizeof(data_specialspan_t *)); 195 | } 196 | 197 | dwin->numcontent -= delta; 198 | if (dwin->updatemark >= lastfill) 199 | dwin->updatemark -= delta; 200 | else 201 | dwin->updatemark = 0; 202 | } 203 | 204 | -------------------------------------------------------------------------------- /rgmisc.c: -------------------------------------------------------------------------------- 1 | /* rgmisc.c: Miscellaneous functions 2 | for RemGlk, remote-procedure-call implementation of the Glk API. 3 | Designed by Andrew Plotkin 4 | http://eblong.com/zarf/glk/ 5 | */ 6 | 7 | #include 8 | #include 9 | #include 10 | #include "glk.h" 11 | #include "remglk.h" 12 | #include "rgdata.h" 13 | 14 | data_supportcaps_t gli_supportcaps; 15 | 16 | static unsigned char char_tolower_table[256]; 17 | static unsigned char char_toupper_table[256]; 18 | 19 | gidispatch_rock_t (*gli_register_obj)(void *obj, glui32 objclass) = NULL; 20 | void (*gli_unregister_obj)(void *obj, glui32 objclass, gidispatch_rock_t objrock) = NULL; 21 | 22 | gidispatch_rock_t (*gli_register_arr)(void *array, glui32 len, char *typecode) = NULL; 23 | void (*gli_unregister_arr)(void *array, glui32 len, char *typecode, gidispatch_rock_t objrock) = NULL; 24 | 25 | long (*gli_dispatch_locate_arr)(void *array, glui32 len, char *typecode, gidispatch_rock_t objrock, int *elemsizeref) = NULL; 26 | gidispatch_rock_t (*gli_dispatch_restore_arr)(long bufkey, glui32 len, char *typecode, void **arrayref) = NULL; 27 | 28 | /* Set up things. This is called from main(). */ 29 | void gli_initialize_misc(data_supportcaps_t *supportcaps) 30 | { 31 | int ix; 32 | int res; 33 | 34 | gli_supportcaps = *supportcaps; 35 | 36 | /* Initialize the to-uppercase and to-lowercase tables. These should 37 | *not* be localized to a platform-native character set! They are 38 | intended to work on Latin-1 data, and the code below correctly 39 | sets up the tables for that character set. */ 40 | 41 | for (ix=0; ix<256; ix++) { 42 | char_toupper_table[ix] = ix; 43 | char_tolower_table[ix] = ix; 44 | } 45 | for (ix=0; ix<256; ix++) { 46 | if (ix >= 'A' && ix <= 'Z') { 47 | res = ix + ('a' - 'A'); 48 | } 49 | else if (ix >= 0xC0 && ix <= 0xDE && ix != 0xD7) { 50 | res = ix + 0x20; 51 | } 52 | else { 53 | res = 0; 54 | } 55 | if (res) { 56 | char_tolower_table[ix] = res; 57 | char_toupper_table[res] = ix; 58 | } 59 | } 60 | 61 | } 62 | 63 | void glk_exit() 64 | { 65 | //### glk_request_timer_events(0)? 66 | gli_windows_update(NULL, TRUE, TRUE); 67 | gli_streams_close_all(); 68 | 69 | if (gli_debugger) 70 | gidebug_announce_cycle(gidebug_cycle_End); 71 | 72 | exit(0); 73 | } 74 | 75 | void glk_set_interrupt_handler(void (*func)(void)) 76 | { 77 | gli_interrupt_handler = func; 78 | } 79 | 80 | void glk_tick() 81 | { 82 | /* Nothing to do here. */ 83 | } 84 | 85 | void gidispatch_set_object_registry( 86 | gidispatch_rock_t (*regi)(void *obj, glui32 objclass), 87 | void (*unregi)(void *obj, glui32 objclass, gidispatch_rock_t objrock)) 88 | { 89 | window_t *win; 90 | stream_t *str; 91 | fileref_t *fref; 92 | 93 | gli_register_obj = regi; 94 | gli_unregister_obj = unregi; 95 | 96 | if (gli_register_obj) { 97 | /* It's now necessary to go through all existing objects, and register 98 | them. */ 99 | for (win = glk_window_iterate(NULL, NULL); 100 | win; 101 | win = glk_window_iterate(win, NULL)) { 102 | win->disprock = (*gli_register_obj)(win, gidisp_Class_Window); 103 | } 104 | for (str = glk_stream_iterate(NULL, NULL); 105 | str; 106 | str = glk_stream_iterate(str, NULL)) { 107 | str->disprock = (*gli_register_obj)(str, gidisp_Class_Stream); 108 | } 109 | for (fref = glk_fileref_iterate(NULL, NULL); 110 | fref; 111 | fref = glk_fileref_iterate(fref, NULL)) { 112 | fref->disprock = (*gli_register_obj)(fref, gidisp_Class_Fileref); 113 | } 114 | } 115 | } 116 | 117 | void gidispatch_set_retained_registry( 118 | gidispatch_rock_t (*regi)(void *array, glui32 len, char *typecode), 119 | void (*unregi)(void *array, glui32 len, char *typecode, 120 | gidispatch_rock_t objrock)) 121 | { 122 | gli_register_arr = regi; 123 | gli_unregister_arr = unregi; 124 | } 125 | 126 | gidispatch_rock_t gidispatch_get_objrock(void *obj, glui32 objclass) 127 | { 128 | switch (objclass) { 129 | case gidisp_Class_Window: 130 | return ((window_t *)obj)->disprock; 131 | case gidisp_Class_Stream: 132 | return ((stream_t *)obj)->disprock; 133 | case gidisp_Class_Fileref: 134 | return ((fileref_t *)obj)->disprock; 135 | default: { 136 | gidispatch_rock_t dummy; 137 | dummy.num = 0; 138 | return dummy; 139 | } 140 | } 141 | } 142 | 143 | void gidispatch_set_autorestore_registry( 144 | long (*locatearr)(void *array, glui32 len, char *typecode, 145 | gidispatch_rock_t objrock, int *elemsizeref), 146 | gidispatch_rock_t (*restorearr)(long bufkey, glui32 len, 147 | char *typecode, void **arrayref)) 148 | { 149 | gli_dispatch_locate_arr = locatearr; 150 | gli_dispatch_restore_arr = restorearr; 151 | } 152 | 153 | unsigned char glk_char_to_lower(unsigned char ch) 154 | { 155 | return char_tolower_table[ch]; 156 | } 157 | 158 | unsigned char glk_char_to_upper(unsigned char ch) 159 | { 160 | return char_toupper_table[ch]; 161 | } 162 | 163 | void gli_display_warning(char *msg) 164 | { 165 | if (pref_stderr) { 166 | fprintf(stderr, "Glk library error: %s\n", msg); 167 | } 168 | else { 169 | printf("{\"type\":\"error\", \"message\":"); 170 | print_string_json(msg, stdout); 171 | printf("}\n"); 172 | } 173 | printf("\n"); /* blank line after stanza */ 174 | fflush(stdout); 175 | } 176 | 177 | void gli_display_error(char *msg) 178 | { 179 | if (pref_stderr) { 180 | fprintf(stderr, "%s\n", msg); 181 | } 182 | else { 183 | printf("{\"type\":\"error\", \"message\":"); 184 | print_string_json(msg, stdout); 185 | printf("}\n"); 186 | } 187 | printf("\n"); /* blank line after stanza */ 188 | fflush(stdout); 189 | exit(1); 190 | } 191 | 192 | #ifdef NO_MEMMOVE 193 | 194 | void *memmove(void *destp, void *srcp, int n) 195 | { 196 | char *dest = (char *)destp; 197 | char *src = (char *)srcp; 198 | 199 | if (dest < src) { 200 | for (; n > 0; n--) { 201 | *dest = *src; 202 | dest++; 203 | src++; 204 | } 205 | } 206 | else if (dest > src) { 207 | src += n; 208 | dest += n; 209 | for (; n > 0; n--) { 210 | dest--; 211 | src--; 212 | *dest = *src; 213 | } 214 | } 215 | 216 | return destp; 217 | } 218 | 219 | #endif /* NO_MEMMOVE */ 220 | -------------------------------------------------------------------------------- /cgdate.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include "glk.h" 7 | #include "remglk.h" 8 | 9 | /* This file is copied directly from the cheapglk package. */ 10 | 11 | #ifdef GLK_MODULE_DATETIME 12 | 13 | #ifdef NO_TIMEGM_AVAIL 14 | extern time_t timegm(struct tm *tm); 15 | #endif /* NO_TIMEGM_AVAIL */ 16 | 17 | /* Copy a POSIX tm structure to a glkdate. */ 18 | static void gli_date_from_tm(glkdate_t *date, struct tm *tm) 19 | { 20 | date->year = 1900 + tm->tm_year; 21 | date->month = 1 + tm->tm_mon; 22 | date->day = tm->tm_mday; 23 | date->weekday = tm->tm_wday; 24 | date->hour = tm->tm_hour; 25 | date->minute = tm->tm_min; 26 | date->second = tm->tm_sec; 27 | } 28 | 29 | /* Copy a glkdate to a POSIX tm structure. 30 | This is used in the "glk_date_to_..." functions, which are supposed 31 | to normalize the glkdate. We're going to rely on the mktime() / 32 | timegm() functions to do that -- except they don't handle microseconds. 33 | So we'll have to do that normalization here, adjust the tm_sec value, 34 | and return the normalized number of microseconds. 35 | */ 36 | static glsi32 gli_date_to_tm(glkdate_t *date, struct tm *tm) 37 | { 38 | glsi32 microsec; 39 | 40 | bzero(tm, sizeof(*tm)); 41 | tm->tm_year = date->year - 1900; 42 | tm->tm_mon = date->month - 1; 43 | tm->tm_mday = date->day; 44 | tm->tm_wday = date->weekday; 45 | tm->tm_hour = date->hour; 46 | tm->tm_min = date->minute; 47 | tm->tm_sec = date->second; 48 | microsec = date->microsec; 49 | 50 | if (microsec >= 1000000) { 51 | tm->tm_sec += (microsec / 1000000); 52 | microsec = microsec % 1000000; 53 | } 54 | else if (microsec < 0) { 55 | microsec = -1 - microsec; 56 | tm->tm_sec -= (1 + microsec / 1000000); 57 | microsec = 999999 - (microsec % 1000000); 58 | } 59 | 60 | return microsec; 61 | } 62 | 63 | /* Convert a Unix timestamp, along with a microseconds value, to 64 | a glktimeval. 65 | */ 66 | static void gli_timestamp_to_time(time_t timestamp, glsi32 microsec, 67 | glktimeval_t *time) 68 | { 69 | if (sizeof(timestamp) <= 4) { 70 | /* This platform has 32-bit time, but we can't do anything 71 | about that. Hope it's not 2038 yet. */ 72 | if (timestamp >= 0) 73 | time->high_sec = 0; 74 | else 75 | time->high_sec = -1; 76 | time->low_sec = timestamp; 77 | } 78 | else { 79 | /* The cast to int64_t shouldn't be necessary, but it 80 | suppresses a pointless warning in the 32-bit case. 81 | (Remember that we won't be executing this line in the 82 | 32-bit case.) */ 83 | time->high_sec = (((int64_t)timestamp) >> 32) & 0xFFFFFFFF; 84 | time->low_sec = timestamp & 0xFFFFFFFF; 85 | } 86 | 87 | time->microsec = microsec; 88 | } 89 | 90 | /* Divide a Unix timestamp by a (positive) value. */ 91 | static glsi32 gli_simplify_time(time_t timestamp, glui32 factor) 92 | { 93 | /* We want to round towards negative infinity, which takes a little 94 | bit of fussing. */ 95 | if (timestamp >= 0) { 96 | return timestamp / (time_t)factor; 97 | } 98 | else { 99 | return -1 - (((time_t)-1 - timestamp) / (time_t)factor); 100 | } 101 | } 102 | 103 | void glk_current_time(glktimeval_t *time) 104 | { 105 | struct timespec ts; 106 | 107 | if (!timespec_get(&ts, TIME_UTC)) { 108 | gli_timestamp_to_time(0, 0, time); 109 | gli_strict_warning("current_time: timespec_get() failed."); 110 | return; 111 | } 112 | 113 | gli_timestamp_to_time(ts.tv_sec, ts.tv_nsec/1000, time); 114 | } 115 | 116 | glsi32 glk_current_simple_time(glui32 factor) 117 | { 118 | struct timespec ts; 119 | 120 | if (factor == 0) { 121 | gli_strict_warning("current_simple_time: factor cannot be zero."); 122 | return 0; 123 | } 124 | 125 | if (!timespec_get(&ts, TIME_UTC)) { 126 | gli_strict_warning("current_simple_time: timespec_get() failed."); 127 | return 0; 128 | } 129 | 130 | return gli_simplify_time(ts.tv_sec, factor); 131 | } 132 | 133 | void glk_time_to_date_utc(glktimeval_t *time, glkdate_t *date) 134 | { 135 | time_t timestamp; 136 | struct tm tm; 137 | 138 | timestamp = time->low_sec; 139 | if (sizeof(timestamp) > 4) { 140 | timestamp += ((int64_t)time->high_sec << 32); 141 | } 142 | 143 | gmtime_r(×tamp, &tm); 144 | 145 | gli_date_from_tm(date, &tm); 146 | date->microsec = time->microsec; 147 | } 148 | 149 | void glk_time_to_date_local(glktimeval_t *time, glkdate_t *date) 150 | { 151 | time_t timestamp; 152 | struct tm tm; 153 | 154 | timestamp = time->low_sec; 155 | if (sizeof(timestamp) > 4) { 156 | timestamp += ((int64_t)time->high_sec << 32); 157 | } 158 | 159 | localtime_r(×tamp, &tm); 160 | 161 | gli_date_from_tm(date, &tm); 162 | date->microsec = time->microsec; 163 | } 164 | 165 | void glk_simple_time_to_date_utc(glsi32 time, glui32 factor, 166 | glkdate_t *date) 167 | { 168 | time_t timestamp = (time_t)time * factor; 169 | struct tm tm; 170 | 171 | gmtime_r(×tamp, &tm); 172 | 173 | gli_date_from_tm(date, &tm); 174 | date->microsec = 0; 175 | } 176 | 177 | void glk_simple_time_to_date_local(glsi32 time, glui32 factor, 178 | glkdate_t *date) 179 | { 180 | time_t timestamp = (time_t)time * factor; 181 | struct tm tm; 182 | 183 | localtime_r(×tamp, &tm); 184 | 185 | gli_date_from_tm(date, &tm); 186 | date->microsec = 0; 187 | } 188 | 189 | void glk_date_to_time_utc(glkdate_t *date, glktimeval_t *time) 190 | { 191 | time_t timestamp; 192 | struct tm tm; 193 | glsi32 microsec; 194 | 195 | microsec = gli_date_to_tm(date, &tm); 196 | /* The timegm function is not standard POSIX. If it's not available 197 | on your platform, try setting the env var "TZ" to "", calling 198 | mktime(), and then resetting "TZ". */ 199 | tm.tm_isdst = 0; 200 | timestamp = timegm(&tm); 201 | 202 | gli_timestamp_to_time(timestamp, microsec, time); 203 | } 204 | 205 | void glk_date_to_time_local(glkdate_t *date, glktimeval_t *time) 206 | { 207 | time_t timestamp; 208 | struct tm tm; 209 | glsi32 microsec; 210 | 211 | microsec = gli_date_to_tm(date, &tm); 212 | tm.tm_isdst = -1; 213 | timestamp = mktime(&tm); 214 | 215 | gli_timestamp_to_time(timestamp, microsec, time); 216 | } 217 | 218 | glsi32 glk_date_to_simple_time_utc(glkdate_t *date, glui32 factor) 219 | { 220 | time_t timestamp; 221 | struct tm tm; 222 | 223 | if (factor == 0) { 224 | gli_strict_warning("date_to_simple_time_utc: factor cannot be zero."); 225 | return 0; 226 | } 227 | 228 | gli_date_to_tm(date, &tm); 229 | /* The timegm function is not standard POSIX. If it's not available 230 | on your platform, try setting the env var "TZ" to "", calling 231 | mktime(), and then resetting "TZ". */ 232 | tm.tm_isdst = 0; 233 | timestamp = timegm(&tm); 234 | 235 | return gli_simplify_time(timestamp, factor); 236 | } 237 | 238 | glsi32 glk_date_to_simple_time_local(glkdate_t *date, glui32 factor) 239 | { 240 | time_t timestamp; 241 | struct tm tm; 242 | 243 | if (factor == 0) { 244 | gli_strict_warning("date_to_simple_time_local: factor cannot be zero."); 245 | return 0; 246 | } 247 | 248 | gli_date_to_tm(date, &tm); 249 | tm.tm_isdst = -1; 250 | timestamp = mktime(&tm); 251 | 252 | return gli_simplify_time(timestamp, factor); 253 | } 254 | 255 | #ifdef NO_TIMEGM_AVAIL 256 | /* If you have no timegm() function, you can #define NO_TIMEGM_AVAIL to 257 | get this definition. */ 258 | 259 | time_t timegm(struct tm *tm) 260 | { 261 | time_t res; 262 | char *origtz; 263 | 264 | origtz = getenv("TZ"); 265 | setenv("TZ", "", 1); 266 | tzset(); 267 | res = mktime(tm); 268 | if (origtz) 269 | setenv("TZ", origtz, 1); 270 | else 271 | unsetenv("TZ"); 272 | tzset(); 273 | 274 | return res; 275 | } 276 | 277 | #endif /* NO_TIMEGM_AVAIL */ 278 | 279 | 280 | #endif /* GLK_MODULE_DATETIME */ 281 | -------------------------------------------------------------------------------- /gi_debug.h: -------------------------------------------------------------------------------- 1 | #ifndef _GI_DEBUG_H 2 | #define _GI_DEBUG_H 3 | 4 | /* gi_debug.h: Debug feature layer for Glk API. 5 | gi_debug version 0.9.5. 6 | Designed by Andrew Plotkin 7 | http://eblong.com/zarf/glk/ 8 | 9 | This file is copyright 2014-7 by Andrew Plotkin. It is 10 | distributed under the MIT license; see the "LICENSE" file. 11 | 12 | ------------------------------------------------ 13 | 14 | The debug module allows a Glk library to send out-of-band debug 15 | commands to the game program it's linked to. The program returns 16 | debug output to the library, which can then display it. 17 | 18 | (Note: 98% of the time, the "game program" is an IF interpreter 19 | such as Glulxe. In such cases, debug commands are handled by the 20 | interpreter; they do *not* get passed through to the interpreted 21 | game file. Debug commands may do things like pause, inspect, or 22 | change the state of the interpreted game.) 23 | 24 | As with all UI decision, the interface of the debug feature is 25 | left up to the Glk library. Abstractly, we imagine a "debug 26 | console" window with its own input line and scrolling text output. 27 | 28 | (If at all possible, avoid trying to parse debug commands out of 29 | regular game input! The CheapGlk library does this, but that's 30 | because it's cheap. It's much better to provide a separate window 31 | outside the regular game UI.) 32 | 33 | * Configuration 34 | 35 | The debug feature is cooperative: both the library and the game 36 | must support it for debug commands to work. This requires a dance 37 | of #ifdefs to make sure that everything builds in all 38 | configurations. 39 | 40 | (This is why the gi_debug.c and .h files are so tiny! All they do 41 | is glue together Glk library and game (interpreter) code.) 42 | 43 | Library side: If the library supports debugging, the 44 | GIDEBUG_LIBRARY_SUPPORT #define in this header (gi_debug.h) will 45 | be present (not commented out). By doing this, the library 46 | declares that it offers the functions gidebug_output() and 47 | gidebug_pause(). 48 | 49 | Older Glk libraries do not include this header at all. Therefore, 50 | a game (interpreter) should have its own configuration option. 51 | For example, in Glulxe, you define VM_DEBUGGER when compiling with 52 | a library that has this header and GIDEBUG_LIBRARY_SUPPORT defined. 53 | When building with an older library (or a library which comments 54 | out the GIDEBUG_LIBRARY_SUPPORT line), you don't define VM_DEBUGGER, 55 | and then the interpreter does not attempt to call debug APIs. 56 | 57 | Game (interpreter) side: If the interpreter supports debug commands, 58 | it should call gidebug_debugging_available() in its startup code. 59 | (See unixstrt.c in the Glulxe source.) If it does not do this, the 60 | library knows that debug commands cannot be handled; it should 61 | disable or hide the "debug console" UI. 62 | 63 | * Game responsibilities 64 | 65 | When the game calls gidebug_debugging_available(), it passes two 66 | callbacks: one to handle debug commands, and one to be notified 67 | at various points in the game's life-cycle. (See below.) 68 | 69 | The command callback should execute the command. The syntax of 70 | debug commands is entirely up to the game. Any results should be 71 | reported via gidebug_output(), which will display them in the 72 | debug console. 73 | 74 | The cycle callback is optional. The game might use it to compute 75 | command timing and report it via gidebug_output(). 76 | 77 | The game may call gidebug_output() at any time; it doesn't have to 78 | be the result of a command. For example, a game crash message could 79 | be reported this way. However, remember that not all Glk libraries 80 | support the debug console; even if it exists, the player might not 81 | be watching it. Assume that game authors know about the debug system, 82 | but players in general do not. 83 | 84 | The game may call gidebug_pause() to stop execution for debugging. 85 | (Glulxe does this on any crash, or if the game hits a @debugtrap 86 | opcode.) This function accepts and executes debugging commands 87 | until the user signals that it's time to continue execution. 88 | 89 | * Library responsibilities 90 | 91 | The library must implement gidebug_output(), to send a line of 92 | text to the debug console, and gidebug_pause(), to stop and handle 93 | debug commands, as described above. 94 | 95 | When the user enters a command in the debug console, the library 96 | should pass it (as a string) to gidebug_perform_command(). It 97 | will be relayed to the game's command callback. 98 | 99 | The library should call gidebug_announce_cycle() at various points 100 | in the game's life-cycle. The argument will be relayed to the 101 | game's cycle callback. 102 | 103 | The library should call and pass... 104 | 105 | - gidebug_cycle_Start: just before glk_main() begins 106 | - gidebug_cycle_End: when glk_exit() is called or glk_main() returns 107 | - gidebug_cycle_InputWait: when glk_select() begins 108 | - gidebug_cycle_InputAccept: when glk_select() returns 109 | - gidebug_cycle_DebugPause: when gidebug_pause() begins 110 | - gidebug_cycle_DebugUnpause: when gidebug_pause() ends 111 | 112 | */ 113 | 114 | 115 | /* Uncomment if the library supports a UI for debug commands. 116 | Comment it out if the library doesn't. */ 117 | #define GIDEBUG_LIBRARY_SUPPORT (1) 118 | 119 | typedef enum gidebug_cycle_enum { 120 | gidebug_cycle_Start = 1, 121 | gidebug_cycle_End = 2, 122 | gidebug_cycle_InputWait = 3, 123 | gidebug_cycle_InputAccept = 4, 124 | gidebug_cycle_DebugPause = 5, 125 | gidebug_cycle_DebugUnpause = 6, 126 | } gidebug_cycle; 127 | 128 | typedef int (*gidebug_cmd_handler)(char *text); 129 | typedef void (*gidebug_cycle_handler)(int cycle); 130 | 131 | /* The gidebug-layer functions are always available (assuming this header 132 | exists!) The game should have a compile-time option (e.g. VM_DEBUGGER) 133 | so as not to rely on this header. */ 134 | 135 | /* The game calls this if it offers debug commands. (The library may 136 | or may not make use of them.) 137 | 138 | The cmdhandler argument must be a function that accepts a debug 139 | command (a UTF-8 string) and executes it, displaying output via 140 | gidebug_output(). The function should return nonzero for a "continue" 141 | command (only relevant inside gidebug_pause()). 142 | 143 | The cyclehandler argument should be a function to be notified 144 | when the game starts, stops, and blocks for input. (This is optional; 145 | pass NULL if not needed.) 146 | */ 147 | extern void gidebug_debugging_available(gidebug_cmd_handler cmdhandler, gidebug_cycle_handler cyclehandler); 148 | 149 | /* The library calls this to check whether the game accepts debug commands. 150 | (Returns nonzero if the game has called gidebug_debugging_available(). 151 | If this returns zero, the library should disable or hide the debug 152 | console.) 153 | */ 154 | extern int gidebug_debugging_is_available(void); 155 | 156 | /* The library calls this when the user enters a command in the debug 157 | console. The command will be passed along to the game's cmdhandler, 158 | if one was supplied. This will return nonzero for a "continue" 159 | command (only relevant inside gidebug_pause()). 160 | 161 | This may only be called when the game is waiting for input! This 162 | means one of two circumstances: while inside glk_select(), or 163 | while inside gidebug_pause(). If you call it at any other time, 164 | you've made some kind of horrible threading mistake. 165 | */ 166 | extern int gidebug_perform_command(char *cmd); 167 | 168 | /* The library calls this at various points in the game's life-cycle. 169 | The argument will be passed along to the game's cyclehandler, 170 | if one was supplied. 171 | */ 172 | extern void gidebug_announce_cycle(gidebug_cycle cycle); 173 | 174 | #if GIDEBUG_LIBRARY_SUPPORT 175 | 176 | /* These functions must be implemented in the library. (If the library 177 | has declared debug support.) */ 178 | 179 | /* Send a line of text to the debug console. The text will be a single line 180 | (no newlines), in UTF-8. 181 | */ 182 | extern void gidebug_output(char *text); 183 | 184 | /* Block and wait for debug commands. The library should accept debug 185 | commands and pass them to gidebug_perform_command(), repeatedly, 186 | until that function returns nonzero. It may also stop of its own 187 | accord (say, when an "unpause" menu item is triggered). 188 | 189 | This should call gidebug_announce_cycle(gidebug_cycle_DebugPause) 190 | upon entry, and the same with gidebug_cycle_DebugUnpause upon exit. 191 | */ 192 | extern void gidebug_pause(void); 193 | 194 | #endif /* GIDEBUG_LIBRARY_SUPPORT */ 195 | 196 | #endif /* _GI_DEBUG_H */ 197 | -------------------------------------------------------------------------------- /rgdata.h: -------------------------------------------------------------------------------- 1 | /* rgdata.h: JSON data structure header 2 | for RemGlk, remote-procedure-call implementation of the Glk API. 3 | Designed by Andrew Plotkin 4 | http://eblong.com/zarf/glk/ 5 | */ 6 | 7 | /* There are two levels of data structures here. The high-level ones 8 | (data_event_t, data_update_t, data_window_t, etc) are built and accepted 9 | by the other parts of the library. 10 | 11 | The low-level structure, data_raw_t, is defined and used only inside 12 | rgdata.c. It maps directly to and from JSON objects. 13 | 14 | Every data structure has a print() method, which sends it to stdout 15 | as a JSON structure. 16 | */ 17 | 18 | typedef enum DTag_enum { 19 | dtag_Unknown = 0, 20 | dtag_Init = 1, 21 | dtag_Refresh = 2, 22 | dtag_Line = 3, 23 | dtag_Char = 4, 24 | dtag_Arrange = 5, 25 | dtag_Redraw = 6, 26 | dtag_Hyperlink = 7, 27 | dtag_Timer = 8, 28 | dtag_SpecialResponse = 9, 29 | dtag_DebugInput = 10, 30 | dtag_Mouse = 11, 31 | } DTag; 32 | 33 | /* gen_list_t: A boring little structure which holds a dynamic list of 34 | void pointers. Several of the high-level data objects use these 35 | to store lists of other high-level data objects. (You embed a 36 | gen_list_t directly, rather than a pointer to it.) */ 37 | typedef struct gen_list_struct { 38 | void **list; 39 | int count; 40 | int allocsize; 41 | } gen_list_t; 42 | 43 | typedef struct data_event_struct data_event_t; 44 | typedef struct data_update_struct data_update_t; 45 | typedef struct data_window_struct data_window_t; 46 | typedef struct data_input_struct data_input_t; 47 | typedef struct data_line_struct data_line_t; 48 | typedef struct data_span_struct data_span_t; 49 | typedef struct data_specialspan_struct data_specialspan_t; 50 | 51 | /* data_metrics_t: Defines the display metrics. 52 | We have to support real values for all of these fields. 53 | (GlkOte is famous for sending noninteger metrics when the browser zoom 54 | changes.) */ 55 | struct data_metrics_struct { 56 | double width, height; 57 | double outspacingx, outspacingy; 58 | double inspacingx, inspacingy; 59 | double gridcharwidth, gridcharheight; 60 | double gridmarginx, gridmarginy; 61 | double buffercharwidth, buffercharheight; 62 | double buffermarginx, buffermarginy; 63 | double graphicsmarginx, graphicsmarginy; 64 | }; 65 | 66 | /* data_supportcaps_t: List of I/O capabilities of the client. */ 67 | struct data_supportcaps_struct { 68 | int timer; 69 | int hyperlinks; 70 | int graphics; 71 | int graphicswin; 72 | int graphicsext; 73 | int sound; 74 | }; 75 | 76 | /* data_event_t: Represents an input event (either the initial setup event, 77 | or user input). */ 78 | struct data_event_struct { 79 | DTag dtag; 80 | glsi32 gen; 81 | glui32 window; 82 | glui32 charvalue; 83 | glui32 *linevalue; 84 | glui32 linelen; 85 | glui32 terminator; 86 | glui32 linkvalue; 87 | glui32 mousex; 88 | glui32 mousey; 89 | data_metrics_t *metrics; 90 | data_supportcaps_t *supportcaps; 91 | }; 92 | 93 | /* data_update_t: Represents a complete output update, including what 94 | happened to all the windows this cycle. */ 95 | struct data_update_struct { 96 | glsi32 gen; 97 | int usewindows; 98 | gen_list_t windows; /* data_window_t */ 99 | gen_list_t contents; /* data_content_t */ 100 | int useinputs; 101 | gen_list_t inputs; /* data_event_t */ 102 | int includetimer; 103 | glui32 timer; 104 | data_specialreq_t *specialreq; 105 | gen_list_t debuglines; /* char* (null-terminated UTF8) */ 106 | int disable; 107 | int exit; 108 | }; 109 | 110 | /* data_window_t: Represents one window, either newly created, resized, or 111 | repositioned. */ 112 | struct data_window_struct { 113 | glui32 window; 114 | glui32 type; 115 | glui32 rock; 116 | grect_t size; 117 | glui32 gridwidth, gridheight; 118 | }; 119 | 120 | /* data_input_t: Represents the input request of one window. */ 121 | struct data_input_struct { 122 | glui32 window; 123 | glsi32 evtype; 124 | glsi32 gen; 125 | glui32 *initstr; 126 | glui32 initlen; 127 | glui32 maxlen; 128 | int cursorpos; /* only for grids */ 129 | glsi32 xpos, ypos; /* only if cursorpos */ 130 | int hyperlink; 131 | int mouse; 132 | }; 133 | 134 | /* data_content_t: Represents the output changes of one window (text 135 | updates). Also used for graphics window updates, because that was 136 | easiest. */ 137 | struct data_content_struct { 138 | glui32 window; 139 | glui32 type; /* window type */ 140 | gen_list_t lines; /* data_line_t */ 141 | int clear; 142 | }; 143 | 144 | /* data_line_t: One line of text in a data_content_t. This is used for 145 | both grid windows and buffer windows. (In a buffer window, a "line" 146 | is a complete paragraph.) */ 147 | struct data_line_struct { 148 | glui32 linenum; 149 | int append; 150 | int flowbreak; 151 | data_span_t *spans; 152 | int count; 153 | int allocsize; 154 | }; 155 | 156 | /* data_span_t: One style-span of text in a data_line_t. */ 157 | struct data_span_struct { 158 | short style; 159 | glui32 hyperlink; 160 | glui32 *str; /* This will always be a reference to existing data. 161 | Do not free. */ 162 | long len; 163 | data_specialspan_t *special; /* Do not free. */ 164 | }; 165 | 166 | typedef enum SpecialType_enum { 167 | specialtype_None = 0, 168 | specialtype_FlowBreak = 1, 169 | specialtype_Image = 2, 170 | specialtype_SetColor = 3, 171 | specialtype_Fill = 4, 172 | } SpecialType; 173 | 174 | /* data_specialspan_t: Extra things that a data_span_t can represent. 175 | Not all these fields are used for all types. */ 176 | struct data_specialspan_struct { 177 | SpecialType type; 178 | glui32 image; /* (Image) */ 179 | glui32 chunktype; /* (Image) JPEG or PNG */ 180 | int hasdimensions; /* (Fill) */ 181 | glui32 xpos; /* (Fill, Image in graphicswin) */ 182 | glui32 ypos; /* (Fill, Image in graphicswin) */ 183 | glui32 width; /* (Fill, Image) */ 184 | glui32 height; /* (Fill, Image) */ 185 | double widthratio; /* (Image in bufferwin) */ 186 | double aspectwidth; /* (Image in bufferwin) */ 187 | double aspectheight; /* (Image in bufferwin) */ 188 | double winmaxwidth; /* (Image in bufferwin) 0.0 will omit this from the JSON (which means the same as 1.0 really); a negative value means an explicit null. */ 189 | glui32 alignment; /* (Image in bufferwin) */ 190 | glui32 hyperlink; /* (Image in bufferwin) */ 191 | char *alttext; /* (Image) Reference to existing data. */ 192 | int hascolor; /* (SetColor, Fill) */ 193 | glui32 color; /* (SetColor, Fill) */ 194 | }; 195 | 196 | /* data_specialreq_t: A special input request. */ 197 | struct data_specialreq_struct { 198 | glui32 filemode; 199 | glui32 filetype; 200 | char *gameid; /* may be null */ 201 | }; 202 | 203 | /* data_tempbufinfo_t: Temporary storage during autorestore. */ 204 | struct data_tempbufinfo_struct { 205 | unsigned char *bufdata; 206 | glui32 *ubufdata; 207 | long bufdatalen; 208 | long bufkey; 209 | glui32 bufptr, bufend, bufeof; 210 | }; 211 | 212 | extern void gli_initialize_datainput(void); 213 | 214 | extern void print_ustring_len_json(glui32 *buf, glui32 len, FILE *fl); 215 | extern void print_utf8string_json(char *buf, FILE *fl); 216 | extern void print_string_json(char *buf, FILE *fl); 217 | extern void print_string_len_json(char *buf, int len, FILE *fl); 218 | 219 | extern void gen_list_init(gen_list_t *list); 220 | extern void gen_list_free(gen_list_t *list); 221 | extern void gen_list_append(gen_list_t *list, void *val); 222 | 223 | extern data_metrics_t *data_metrics_alloc(int width, int height); 224 | extern void data_metrics_free(data_metrics_t *metrics); 225 | extern void data_metrics_print(FILE *fl, data_metrics_t *metrics); 226 | extern data_metrics_t *data_metrics_parse(data_raw_t *rawdata); 227 | 228 | extern data_supportcaps_t *data_supportcaps_alloc(void); 229 | extern void data_supportcaps_clear(data_supportcaps_t *supportcaps); 230 | extern void data_supportcaps_merge(data_supportcaps_t *supportcaps, data_supportcaps_t *other); 231 | extern void data_supportcaps_free(data_supportcaps_t *supportcaps); 232 | extern void data_supportcaps_print(FILE *fl, data_supportcaps_t *supportcaps); 233 | extern data_supportcaps_t *data_supportcaps_parse(data_raw_t *rawdata); 234 | 235 | extern data_event_t *data_event_read(void); 236 | extern void data_event_free(data_event_t *data); 237 | extern void data_event_print(data_event_t *data); 238 | 239 | extern data_update_t *data_update_alloc(void); 240 | extern void data_update_free(data_update_t *data); 241 | extern void data_update_print(data_update_t *data); 242 | 243 | extern data_window_t *data_window_alloc(glui32 window, glui32 type, glui32 rock); 244 | extern void data_window_free(data_window_t *data); 245 | extern void data_window_print(data_window_t *data); 246 | 247 | extern data_input_t *data_input_alloc(glui32 window, glui32 evtype); 248 | extern void data_input_free(data_input_t *data); 249 | extern void data_input_print(data_input_t *data); 250 | 251 | extern data_content_t *data_content_alloc(glui32 window, glui32 type); 252 | extern void data_content_free(data_content_t *data); 253 | extern void data_content_print(data_content_t *data); 254 | 255 | extern data_line_t *data_line_alloc(void); 256 | extern void data_line_free(data_line_t *data); 257 | extern void data_line_add_span(data_line_t *data, short style, glui32 hyperlink, glui32 *str, long len); 258 | extern void data_line_add_specialspan(data_line_t *data, data_specialspan_t *special); 259 | extern void data_line_print(data_line_t *data, glui32 wintype); 260 | 261 | extern data_specialspan_t *data_specialspan_alloc(SpecialType type); 262 | extern void data_specialspan_free(data_specialspan_t *data); 263 | extern void data_specialspan_print(data_specialspan_t *dat, glui32 wintype); 264 | extern void data_specialspan_auto_print(FILE *file, data_specialspan_t *dat); 265 | extern data_specialspan_t *data_specialspan_auto_parse(data_raw_t *rawdata); 266 | 267 | extern data_specialreq_t *data_specialreq_alloc(glui32 filemode, glui32 filetype); 268 | extern void data_specialreq_free(data_specialreq_t *data); 269 | extern void data_specialreq_print(data_specialreq_t *data); 270 | 271 | extern data_tempbufinfo_t *data_tempbufinfo_alloc(void); 272 | extern void data_tempbufinfo_free(data_tempbufinfo_t *data); 273 | 274 | extern void data_grect_clear(grect_t *box); 275 | extern void data_grect_print(FILE *file, grect_t *box); 276 | extern void data_grect_parse(data_raw_t *rawdata, grect_t *box); 277 | 278 | -------------------------------------------------------------------------------- /remglk.h: -------------------------------------------------------------------------------- 1 | /* remglk.h: Private header file 2 | for RemGlk, remote-procedure-call implementation of the Glk API. 3 | Designed by Andrew Plotkin 4 | http://eblong.com/zarf/glk/ 5 | */ 6 | 7 | #ifndef REMGLK_H 8 | #define REMGLK_H 9 | 10 | #include "gi_dispa.h" 11 | #include "gi_debug.h" 12 | 13 | #define LIBRARY_VERSION "0.3.2" 14 | 15 | /* We define our own TRUE and FALSE and NULL, because ANSI 16 | is a strange world. */ 17 | #ifndef TRUE 18 | #define TRUE 1 19 | #endif 20 | #ifndef FALSE 21 | #define FALSE 0 22 | #endif 23 | #ifndef NULL 24 | #define NULL 0 25 | #endif 26 | 27 | /* This macro is called whenever the library code catches an error 28 | or illegal operation from the game program. */ 29 | 30 | #define gli_strict_warning(msg) \ 31 | (gli_display_warning(msg)) 32 | 33 | #define gli_fatal_error(msg) \ 34 | (gli_display_error(msg)) 35 | 36 | /* Some useful type declarations. */ 37 | 38 | typedef struct grect_struct { 39 | int left, top; 40 | int right, bottom; 41 | } grect_t; 42 | 43 | #define grect_set_from_size(boxref, wid, hgt) \ 44 | ((boxref)->left = 0, (boxref)->top = 0, \ 45 | (boxref)->right = (wid), (boxref)->bottom = (hgt)) 46 | 47 | typedef struct data_raw_struct data_raw_t; 48 | typedef struct data_metrics_struct data_metrics_t; 49 | typedef struct data_content_struct data_content_t; 50 | typedef struct data_specialreq_struct data_specialreq_t; 51 | typedef struct data_tempbufinfo_struct data_tempbufinfo_t; 52 | typedef struct data_supportcaps_struct data_supportcaps_t; 53 | 54 | typedef struct glk_window_struct window_t; 55 | typedef struct glk_stream_struct stream_t; 56 | typedef struct glk_fileref_struct fileref_t; 57 | 58 | #define MAGIC_WINDOW_NUM (9826) 59 | #define MAGIC_STREAM_NUM (8269) 60 | #define MAGIC_FILEREF_NUM (6982) 61 | 62 | struct glk_window_struct { 63 | glui32 magicnum; 64 | glui32 rock; 65 | glui32 type; 66 | glui32 updatetag; /* numeric tag for the window in output and autosave */ 67 | 68 | grect_t bbox; /* content rectangle, excluding borders */ 69 | window_t *parent; /* pair window which contains this one */ 70 | void *data; /* one of the window_*_t structures */ 71 | 72 | stream_t *str; /* the window stream. */ 73 | stream_t *echostr; /* the window's echo stream, if any. */ 74 | 75 | glui32 inputgen; 76 | int line_request; 77 | int line_request_uni; 78 | int char_request; 79 | int char_request_uni; 80 | int hyperlink_request; 81 | int mouse_request; 82 | 83 | int echo_line_input; /* applies to future line inputs, not the current */ 84 | glui32 terminate_line_input; /* ditto; this is a bitmask of flags */ 85 | 86 | glui32 style; 87 | glui32 hyperlink; 88 | 89 | /* only used in a temporary library_state, while deserializing. */ 90 | data_tempbufinfo_t *tempbufinfo; 91 | 92 | gidispatch_rock_t disprock; 93 | window_t *next, *prev; /* in the big linked list of windows */ 94 | }; 95 | 96 | #define strtype_File (1) 97 | #define strtype_Window (2) 98 | #define strtype_Memory (3) 99 | #define strtype_Resource (4) 100 | 101 | struct glk_stream_struct { 102 | glui32 magicnum; 103 | glui32 rock; 104 | glui32 updatetag; /* numeric tag for the stream in autosave */ 105 | 106 | int type; /* file, window, or memory stream */ 107 | int unicode; /* one-byte or four-byte chars? Not meaningful for windows */ 108 | 109 | glui32 readcount, writecount; 110 | int readable, writable; 111 | 112 | /* for strtype_Window */ 113 | window_t *win; 114 | 115 | /* for strtype_File */ 116 | FILE *file; 117 | glui32 lastop; /* 0, filemode_Write, or filemode_Read */ 118 | char *filename; /* only needed for autosave */ 119 | char *modestr; /* only needed for autosave */ 120 | 121 | /* for strtype_File, strtype_Resource */ 122 | int isbinary; 123 | 124 | /* for strtype_Resource */ 125 | glui32 fileresnum; /* only needed for autosave */ 126 | 127 | /* for strtype_Memory and strtype_Resource. Separate pointers for 128 | one-byte and four-byte streams */ 129 | /* note that buf/ubuf point to memory outside the library. Usually it's owned by the dispatch layer. The other pointers point within buf/ubuf. */ 130 | unsigned char *buf; 131 | unsigned char *bufptr; 132 | unsigned char *bufend; 133 | unsigned char *bufeof; 134 | glui32 *ubuf; 135 | glui32 *ubufptr; 136 | glui32 *ubufend; 137 | glui32 *ubufeof; 138 | glui32 buflen; 139 | gidispatch_rock_t arrayrock; 140 | 141 | /* only used in a temporary library_state, while deserializing. */ 142 | data_tempbufinfo_t *tempbufinfo; 143 | 144 | gidispatch_rock_t disprock; 145 | stream_t *next, *prev; /* in the big linked list of streams */ 146 | }; 147 | 148 | struct glk_fileref_struct { 149 | glui32 magicnum; 150 | glui32 rock; 151 | glui32 updatetag; /* numeric tag for the fileref in autosave */ 152 | 153 | char *filename; 154 | int filetype; 155 | int textmode; 156 | 157 | gidispatch_rock_t disprock; 158 | fileref_t *next, *prev; /* in the big linked list of filerefs */ 159 | }; 160 | 161 | /* A few global variables */ 162 | 163 | extern data_supportcaps_t gli_supportcaps; 164 | extern window_t *gli_rootwin; 165 | extern window_t *gli_focuswin; 166 | extern stream_t *gli_currentstr; 167 | extern void (*gli_interrupt_handler)(void); 168 | 169 | /* The following typedefs are copied from cheapglk.h. They support the 170 | tables declared in cgunigen.c. */ 171 | 172 | typedef glui32 gli_case_block_t[2]; /* upper, lower */ 173 | /* If both are 0xFFFFFFFF, you have to look at the special-case table */ 174 | 175 | typedef glui32 gli_case_special_t[3]; /* upper, lower, title */ 176 | /* Each of these points to a subarray of the unigen_special_array 177 | (in cgunicode.c). In that subarray, element zero is the length, 178 | and that's followed by length unicode values. */ 179 | 180 | typedef glui32 gli_decomp_block_t[2]; /* count, position */ 181 | /* The position points to a subarray of the unigen_decomp_array. 182 | If the count is zero, there is no decomposition. */ 183 | 184 | 185 | extern gidispatch_rock_t (*gli_register_obj)(void *obj, glui32 objclass); 186 | extern void (*gli_unregister_obj)(void *obj, glui32 objclass, gidispatch_rock_t objrock); 187 | extern gidispatch_rock_t (*gli_register_arr)(void *array, glui32 len, char *typecode); 188 | extern void (*gli_unregister_arr)(void *array, glui32 len, char *typecode, gidispatch_rock_t objrock); 189 | extern long (*gli_dispatch_locate_arr)(void *array, glui32 len, char *typecode, gidispatch_rock_t objrock, int *elemsizeref); 190 | extern gidispatch_rock_t (*gli_dispatch_restore_arr)(long bufkey, glui32 len, char *typecode, void **arrayref); 191 | 192 | extern int pref_stderr; 193 | extern int pref_singleturn; 194 | extern int pref_gamefiledir; 195 | extern int pref_onlyfiledir; 196 | extern char *pref_resourceurl; 197 | 198 | extern int gli_get_dataresource_info(int num, void **ptr, glui32 *len, int *isbinary); 199 | 200 | #if GIDEBUG_LIBRARY_SUPPORT 201 | /* Has the user requested debug support? */ 202 | extern int gli_debugger; 203 | #else /* GIDEBUG_LIBRARY_SUPPORT */ 204 | #define gli_debugger (0) 205 | #endif /* GIDEBUG_LIBRARY_SUPPORT */ 206 | 207 | /* Declarations of library internal functions. */ 208 | 209 | extern void gli_initialize_misc(data_supportcaps_t *supportcaps); 210 | 211 | extern void gli_msgline_warning(char *msg); 212 | extern void gli_msgline_error(char *msg); 213 | extern void gli_msgline(char *msg); 214 | extern void gli_msgline_redraw(void); 215 | 216 | extern int gli_msgin_getline(char *prompt, char *buf, int maxlen, int *length); 217 | extern int gli_msgin_getchar(char *prompt, int hilite); 218 | 219 | extern void gli_putchar_utf8(glui32 val, FILE *fl); 220 | extern glui32 gli_parse_utf8(unsigned char *buf, glui32 buflen, 221 | glui32 *out, glui32 outlen); 222 | extern int gli_encode_utf8(glui32 val, char *buf, int len); 223 | 224 | extern void gli_initialize_events(void); 225 | extern void gli_event_store(glui32 type, window_t *win, glui32 val1, glui32 val2); 226 | extern void gli_set_last_event_type(glui32 type); 227 | extern int gli_timer_need_update(glui32 *msec); 228 | extern glui32 gli_timer_get_timing_msec(void); 229 | extern void gli_select_metrics(data_metrics_t *metrics, data_supportcaps_t *supportcaps); 230 | extern char *gli_select_specialrequest(data_specialreq_t *special); 231 | extern void gli_select_imaginary(void); 232 | 233 | extern void gli_initialize_windows(void); 234 | extern void gli_fast_exit(void) GLK_ATTRIBUTE_NORETURN; 235 | extern void gli_display_warning(char *msg); 236 | extern void gli_display_error(char *msg) GLK_ATTRIBUTE_NORETURN; 237 | extern glui32 gli_window_current_generation(void); 238 | extern winid_t glkunix_window_find_by_updatetag(glui32 tag); /* see glkstart.h */ 239 | extern window_t *gli_new_window(glui32 type, glui32 rock); 240 | extern window_t *gli_window_alloc_inactive(void); 241 | extern void gli_window_dealloc_inactive(window_t *win); 242 | extern void gli_delete_window(window_t *win); 243 | extern int gli_windows_update_from_state(window_t **list, int count, window_t *rootwin, glui32 gen); 244 | extern window_t *gli_window_iterate_treeorder(window_t *win); 245 | extern void gli_window_rearrange(window_t *win, grect_t *box, data_metrics_t *metrics); 246 | extern void gli_windows_update(data_specialreq_t *special, int newgeneration, int gameover); 247 | extern void gli_windows_refresh(glui32 fromgen); 248 | extern void gli_windows_metrics_change(data_metrics_t *newmetrics); 249 | extern data_metrics_t *gli_windows_get_metrics(void); 250 | extern void gli_windows_update_metrics(data_metrics_t *newmetrics); 251 | extern void gli_windows_trim_buffers(void); 252 | extern void gli_window_put_char(window_t *win, glui32 ch); 253 | extern void gli_windows_unechostream(stream_t *str); 254 | extern void gli_window_prepare_input(window_t *win, glui32 *buf, glui32 len); 255 | extern void gli_window_accept_line(window_t *win); 256 | extern void gli_print_spaces(int len); 257 | 258 | extern void gcmd_win_change_focus(window_t *win, glui32 arg); 259 | extern void gcmd_win_refresh(window_t *win, glui32 arg); 260 | 261 | extern void gli_initialize_streams(void); 262 | extern stream_t *gli_new_stream(int type, int readable, int writable, 263 | glui32 rock); 264 | extern stream_t *gli_stream_alloc_inactive(void); 265 | extern void gli_stream_dealloc_inactive(stream_t *str); 266 | extern void gli_delete_stream(stream_t *str); 267 | extern int gli_streams_update_from_state(stream_t **list, int count, stream_t *currentstr); 268 | extern stream_t *gli_stream_open_window(window_t *win); 269 | extern strid_t gli_stream_open_pathname(char *pathname, int writemode, 270 | int textmode, glui32 rock); 271 | extern void gli_stream_set_current(stream_t *str); 272 | extern void gli_stream_fill_result(stream_t *str, 273 | stream_result_t *result); 274 | extern void gli_stream_echo_line(stream_t *str, char *buf, glui32 len); 275 | extern void gli_stream_echo_line_uni(stream_t *str, glui32 *buf, glui32 len); 276 | extern void gli_streams_close_all(void); 277 | 278 | extern void gli_initialize_filerefs(void); 279 | extern void gli_fileref_set_working_dir(char *filename); 280 | extern fileref_t *gli_new_fileref(char *filename, glui32 usage, 281 | glui32 rock); 282 | extern fileref_t *gli_fileref_alloc_inactive(void); 283 | extern void gli_fileref_dealloc_inactive(fileref_t *fref); 284 | extern void gli_delete_fileref(fileref_t *fref); 285 | extern int gli_filerefs_update_from_state(fileref_t **list, int count); 286 | 287 | /* A macro that I can't think of anywhere else to put it. */ 288 | 289 | #define gli_event_clearevent(evp) \ 290 | ((evp)->type = evtype_None, \ 291 | (evp)->win = NULL, \ 292 | (evp)->val1 = 0, \ 293 | (evp)->val2 = 0) 294 | 295 | /* A macro which reads and decodes one character of UTF-8. Needs no 296 | explanation, I'm sure. 297 | 298 | Oh, okay. The character will be written to *chptr (so pass in "&ch", 299 | where ch is a glui32 variable). eofcond should be a condition to 300 | evaluate end-of-stream -- true if no more characters are readable. 301 | nextch is a function which reads the next character; this is invoked 302 | exactly as many times as necessary. 303 | 304 | val0, val1, val2, val3 should be glui32 scratch variables. The macro 305 | needs these. Just define them, you don't need to pay attention to them 306 | otherwise. 307 | 308 | The macro itself evaluates to true if ch was successfully set, or 309 | false if something went wrong. (Not enough characters, or an 310 | invalid byte sequence.) 311 | 312 | This is not the worst macro I've ever written, but I forget what the 313 | other one was. 314 | */ 315 | 316 | #define UTF8_DECODE_INLINE(chptr, eofcond, nextch, val0, val1, val2, val3) ( \ 317 | (eofcond ? 0 : ( \ 318 | (((val0=nextch) < 0x80) ? (*chptr=val0, 1) : ( \ 319 | (eofcond ? 0 : ( \ 320 | (((val1=nextch) & 0xC0) != 0x80) ? 0 : ( \ 321 | (((val0 & 0xE0) == 0xC0) ? (*chptr=((val0 & 0x1F) << 6) | (val1 & 0x3F), 1) : ( \ 322 | (eofcond ? 0 : ( \ 323 | (((val2=nextch) & 0xC0) != 0x80) ? 0 : ( \ 324 | (((val0 & 0xF0) == 0xE0) ? (*chptr=(((val0 & 0xF)<<12) & 0x0000F000) | (((val1 & 0x3F)<<6) & 0x00000FC0) | (((val2 & 0x3F)) & 0x0000003F), 1) : ( \ 325 | (((val0 & 0xF0) != 0xF0 || eofcond) ? 0 : (\ 326 | (((val3=nextch) & 0xC0) != 0x80) ? 0 : (*chptr=(((val0 & 0x7)<<18) & 0x1C0000) | (((val1 & 0x3F)<<12) & 0x03F000) | (((val2 & 0x3F)<<6) & 0x000FC0) | (((val3 & 0x3F)) & 0x00003F), 1) \ 327 | )) \ 328 | )) \ 329 | )) \ 330 | )) \ 331 | )) \ 332 | )) \ 333 | )) \ 334 | )) \ 335 | ) 336 | 337 | #ifdef NO_MEMMOVE 338 | extern void *memmove(void *dest, void *src, int n); 339 | #endif /* NO_MEMMOVE */ 340 | 341 | #endif /* REMGLK_H */ 342 | -------------------------------------------------------------------------------- /rgevent.c: -------------------------------------------------------------------------------- 1 | /* rgevent.c: Event handling, including glk_select() and timed input code 2 | for RemGlk, remote-procedure-call implementation of the Glk API. 3 | Designed by Andrew Plotkin 4 | http://eblong.com/zarf/glk/ 5 | */ 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include "glk.h" 14 | #include "remglk.h" 15 | #include "rgdata.h" 16 | 17 | /* A pointer to the place where the pending glk_select() will store its 18 | event. When not inside a glk_select() call, this will be NULL. */ 19 | static event_t *curevent = NULL; 20 | 21 | /* The autosave code needs to peek at this. */ 22 | static glui32 last_event_type; 23 | 24 | /* The current timed-event request, exactly as passed to 25 | glk_request_timer_events(). */ 26 | static glui32 timing_msec; 27 | /* The last timing value that was sent out. (0 means null was sent.) */ 28 | static glui32 last_timing_msec; 29 | /* When the current timer started or last fired. */ 30 | static struct timespec timing_start; 31 | 32 | static glsi32 gli_timer_request_since_start(void); 33 | static char *alloc_utf_buffer(glui32 *ustr, int ulen); 34 | 35 | /* Set up the input system. This is called from main(). */ 36 | void gli_initialize_events() 37 | { 38 | timing_msec = 0; 39 | last_timing_msec = 0; 40 | last_event_type = 0xFFFFFFFF; 41 | } 42 | 43 | void glk_select(event_t *event) 44 | { 45 | curevent = event; 46 | gli_event_clearevent(curevent); 47 | 48 | if (gli_debugger) 49 | gidebug_announce_cycle(gidebug_cycle_InputWait); 50 | 51 | /* Send an update stanza to stdout. We do this before every glk_select, 52 | including at startup, but *not* if we just autorestored. */ 53 | if (last_event_type != 0xFFFFFFFE) { 54 | gli_windows_update(NULL, TRUE, FALSE); 55 | if (pref_singleturn) { 56 | /* Singleton mode mode means that we exit after every output. */ 57 | gli_fast_exit(); 58 | } 59 | } 60 | 61 | while (curevent->type == evtype_None) { 62 | data_event_t *data = data_event_read(); 63 | 64 | window_t *win = NULL; 65 | glui32 val; 66 | 67 | if (data->gen != gli_window_current_generation() && data->dtag != dtag_Refresh) 68 | gli_fatal_error("Input generation number does not match."); 69 | 70 | switch (data->dtag) { 71 | case dtag_Refresh: 72 | /* Repeat the current display state and keep waiting for 73 | a (real) event. */ 74 | gli_windows_refresh(data->gen); 75 | gli_windows_update(NULL, FALSE, FALSE); 76 | if (pref_singleturn) { 77 | gli_fast_exit(); 78 | } 79 | break; 80 | 81 | case dtag_Arrange: 82 | gli_windows_metrics_change(data->metrics); 83 | break; 84 | 85 | case dtag_Redraw: 86 | if (data->window) 87 | win = glkunix_window_find_by_updatetag(data->window); 88 | else 89 | win = NULL; 90 | gli_event_store(evtype_Redraw, win, 0, 0); 91 | break; 92 | 93 | case dtag_Line: 94 | win = glkunix_window_find_by_updatetag(data->window); 95 | if (!win) 96 | break; 97 | if (!win->line_request) 98 | break; 99 | gli_window_prepare_input(win, data->linevalue, data->linelen); 100 | gli_window_accept_line(win); 101 | win->inputgen = 0; 102 | break; 103 | 104 | case dtag_Char: 105 | win = glkunix_window_find_by_updatetag(data->window); 106 | if (!win) 107 | break; 108 | if (!win->char_request) 109 | break; 110 | val = data->charvalue; 111 | if (!win->char_request_uni) { 112 | /* Filter out non-Latin-1 characters, except we also 113 | accept special chars. */ 114 | if (val >= 256 && val < 0xffffffff-keycode_MAXVAL) 115 | val = '?'; 116 | } 117 | win->char_request = FALSE; 118 | win->char_request_uni = FALSE; 119 | win->inputgen = 0; 120 | gli_event_store(evtype_CharInput, win, val, 0); 121 | break; 122 | 123 | case dtag_Hyperlink: 124 | win = glkunix_window_find_by_updatetag(data->window); 125 | if (!win) 126 | break; 127 | if (!win->hyperlink_request) 128 | break; 129 | win->hyperlink_request = FALSE; 130 | gli_event_store(evtype_Hyperlink, win, data->linkvalue, 0); 131 | break; 132 | 133 | case dtag_Mouse: 134 | win = glkunix_window_find_by_updatetag(data->window); 135 | if (!win) 136 | break; 137 | if (!win->mouse_request) 138 | break; 139 | win->mouse_request = FALSE; 140 | gli_event_store(evtype_MouseInput, win, data->mousex, data->mousey); 141 | break; 142 | 143 | case dtag_Timer: 144 | timespec_get(&timing_start, TIME_UTC); 145 | gli_event_store(evtype_Timer, NULL, 0, 0); 146 | break; 147 | 148 | case dtag_DebugInput: 149 | if (gli_debugger) { 150 | /* If debug support is compiled in *and* turned on: 151 | process the command, send an update, and 152 | continue the glk_select. */ 153 | char *allocbuf = alloc_utf_buffer(data->linevalue, data->linelen); 154 | gidebug_perform_command(allocbuf); 155 | free(allocbuf); 156 | 157 | gli_event_clearevent(curevent); 158 | gli_windows_update(NULL, TRUE, FALSE); 159 | break; 160 | } 161 | /* ...else fall through to default behavior. */ 162 | 163 | default: 164 | /* Ignore the event. (The constant is not defined by Glk; 165 | we use it to represent any event whose textual name 166 | is unrecognized.) */ 167 | gli_event_store(0x7FFFFFFF, NULL, 0, 0); 168 | break; 169 | } 170 | 171 | data_event_free(data); 172 | } 173 | 174 | /* An event has occurred; glk_select() is over. */ 175 | gli_windows_trim_buffers(); 176 | last_event_type = curevent->type; 177 | curevent = NULL; 178 | 179 | if (gli_debugger) 180 | gidebug_announce_cycle(gidebug_cycle_InputAccept); 181 | } 182 | 183 | void glk_select_poll(event_t *event) 184 | { 185 | curevent = event; 186 | gli_event_clearevent(curevent); 187 | 188 | /* We can only sensibly check for unfired timer events. */ 189 | /* ### This is not consistent with the modern understanding that 190 | the display layer handles timer events. Might want to just rip 191 | all this timing code out entirely. */ 192 | if (timing_msec) { 193 | glsi32 time = gli_timer_request_since_start(); 194 | if (time >= 0 && time >= timing_msec) { 195 | timespec_get(&timing_start, TIME_UTC); 196 | /* Resend timer request at next update. */ 197 | last_timing_msec = 0; 198 | /* Call it a timer event. */ 199 | curevent->type = evtype_Timer; 200 | } 201 | } 202 | 203 | curevent = NULL; 204 | } 205 | 206 | /* Wait for input, but it has to be a metrics object. Store the result. */ 207 | void gli_select_metrics(data_metrics_t *metrics, data_supportcaps_t *supportcaps) 208 | { 209 | data_event_t *data = data_event_read(); 210 | 211 | if (data->dtag != dtag_Init) 212 | gli_fatal_error("First input event must be 'init'"); 213 | 214 | *metrics = *data->metrics; 215 | 216 | if (data->supportcaps) { 217 | *supportcaps = *data->supportcaps; 218 | } 219 | else { 220 | data_supportcaps_clear(supportcaps); 221 | } 222 | 223 | last_event_type = evtype_Arrange; 224 | 225 | data_event_free(data); 226 | } 227 | 228 | /* Wait for input, but it has to be a special-input response. (Currently 229 | this means a filename prompt.) 230 | Returns a malloced text string or NULL. 231 | */ 232 | char *gli_select_specialrequest(data_specialreq_t *special) 233 | { 234 | char *buf = NULL; 235 | 236 | /* Send an update stanza to stdout. We do this before every 237 | get_by_prompt, but *not* if we just autorestored. */ 238 | if (last_event_type != 0xFFFFFFFE) { 239 | gli_windows_update(special, TRUE, FALSE); 240 | if (pref_singleturn) { 241 | /* Singleton mode mode means that we exit after every output. */ 242 | gli_fast_exit(); 243 | } 244 | } 245 | 246 | while (TRUE) { 247 | data_event_t *data = data_event_read(); 248 | 249 | if (data->gen != gli_window_current_generation()) 250 | gli_fatal_error("Input generation number does not match."); 251 | 252 | if (data->dtag != dtag_SpecialResponse) { 253 | data_event_free(data); 254 | continue; 255 | } 256 | 257 | if (data->linelen && data->linevalue) { 258 | int val; 259 | buf = malloc(data->linelen + 1); 260 | for (val=0; vallinelen; val++) { 261 | glui32 ch = data->linevalue[val]; 262 | if (ch < 0x20 || ch > 0xFF) 263 | ch = '-'; 264 | buf[val] = ch; 265 | } 266 | buf[data->linelen] = '\0'; 267 | } 268 | else { 269 | buf = NULL; 270 | }; 271 | 272 | data_event_free(data); 273 | break; 274 | } 275 | 276 | /* This wasn't an event, but we want to nudge last_event_type. */ 277 | last_event_type = evtype_None; 278 | 279 | return buf; 280 | } 281 | 282 | /* Increment the input counter. This is used with the fixedmetrics 283 | argument, which acts like the library got input. 284 | */ 285 | void gli_select_imaginary() 286 | { 287 | last_event_type = evtype_Arrange; 288 | } 289 | 290 | /* Convert an array of Unicode chars to (null-terminated) UTF-8. 291 | The caller should free this after use. 292 | */ 293 | static char *alloc_utf_buffer(glui32 *ustr, int ulen) 294 | { 295 | /* We do this in a lazy way; we alloc the largest possible buffer. */ 296 | int len = 4*ulen+4; 297 | char *buf = malloc(len); 298 | if (!buf) 299 | return NULL; 300 | 301 | char *ptr = buf; 302 | int ix = 0; 303 | int cx = 0; 304 | while (cx < ulen) { 305 | ix += gli_encode_utf8(ustr[cx], ptr+ix, len-ix); 306 | cx++; 307 | } 308 | 309 | *(ptr+ix) = '\0'; 310 | return buf; 311 | } 312 | 313 | #if GIDEBUG_LIBRARY_SUPPORT 314 | 315 | /* Block and wait for debug commands. The library will accept debug commands 316 | until gidebug_perform_command() returns nonzero. 317 | 318 | This behaves a lot like glk_select(), except that it only handles debug 319 | input, not any of the standard event types. 320 | */ 321 | void gidebug_pause() 322 | { 323 | if (!gli_debugger) 324 | return; 325 | 326 | gidebug_announce_cycle(gidebug_cycle_DebugPause); 327 | 328 | char *allocbuf; 329 | int unpause = FALSE; 330 | 331 | while (!unpause) { 332 | gli_windows_update(NULL, TRUE, FALSE); 333 | 334 | data_event_t *data = data_event_read(); 335 | 336 | if (data->gen != gli_window_current_generation() && data->dtag != dtag_Refresh) 337 | gli_fatal_error("Input generation number does not match."); 338 | 339 | switch (data->dtag) { 340 | case dtag_DebugInput: 341 | allocbuf = alloc_utf_buffer(data->linevalue, data->linelen); 342 | unpause = gidebug_perform_command(allocbuf); 343 | free(allocbuf); 344 | break; 345 | 346 | default: 347 | /* Ignore all non-debug events. */ 348 | break; 349 | } 350 | 351 | } 352 | 353 | gidebug_announce_cycle(gidebug_cycle_DebugUnpause); 354 | } 355 | 356 | #endif /* GIDEBUG_LIBRARY_SUPPORT */ 357 | 358 | /* Various modules can call this to indicate that an event has occurred. 359 | This doesn't try to queue events, but since a single keystroke or 360 | idle event can only cause one event at most, this is fine. */ 361 | void gli_event_store(glui32 type, window_t *win, glui32 val1, glui32 val2) 362 | { 363 | if (curevent) { 364 | curevent->type = type; 365 | curevent->win = win; 366 | curevent->val1 = val1; 367 | curevent->val2 = val2; 368 | } 369 | } 370 | 371 | /* Peek at the last Glk event to come in. Returns 0xFFFFFFFF if we just 372 | started up; 0xFFFFFFFE if we just autorestored. */ 373 | glui32 glkunix_get_last_event_type() 374 | { 375 | return last_event_type; 376 | } 377 | 378 | void gli_set_last_event_type(glui32 type) 379 | { 380 | last_event_type = type; 381 | } 382 | 383 | void glk_request_timer_events(glui32 millisecs) 384 | { 385 | if (!gli_supportcaps.timer) 386 | return; 387 | timing_msec = millisecs; 388 | timespec_get(&timing_start, TIME_UTC); 389 | } 390 | 391 | /* Return whether the timer request has changed since the last call. 392 | If so, also return the request value as *msec. */ 393 | int gli_timer_need_update(glui32 *msec) 394 | { 395 | if (last_timing_msec != timing_msec) { 396 | *msec = timing_msec; 397 | last_timing_msec = timing_msec; 398 | return TRUE; 399 | } 400 | else { 401 | *msec = 0; 402 | return FALSE; 403 | } 404 | } 405 | 406 | glui32 gli_timer_get_timing_msec() 407 | { 408 | return timing_msec; 409 | } 410 | 411 | /* Work out how many milliseconds it has been since timing_start. 412 | If there is no timer, returns -1. */ 413 | static glsi32 gli_timer_request_since_start() 414 | { 415 | struct timespec ts; 416 | 417 | if (!gli_supportcaps.timer) 418 | return -1; 419 | if (!timing_msec) 420 | return -1; 421 | 422 | timespec_get(&ts, TIME_UTC); 423 | 424 | if (ts.tv_sec < timing_start.tv_sec) { 425 | return 0; 426 | } 427 | else if (ts.tv_sec == timing_start.tv_sec) { 428 | if (ts.tv_nsec < timing_start.tv_nsec) 429 | return 0; 430 | return (ts.tv_nsec - timing_start.tv_nsec) / 1000000; 431 | } 432 | else { 433 | glsi32 res = (ts.tv_sec - timing_start.tv_sec) * 1000; 434 | res += ((ts.tv_nsec - timing_start.tv_nsec) / 1000000); 435 | return res; 436 | } 437 | } 438 | -------------------------------------------------------------------------------- /rgfref.c: -------------------------------------------------------------------------------- 1 | /* rgfref.c: File reference objects 2 | for RemGlk, remote-procedure-call implementation of the Glk API. 3 | Designed by Andrew Plotkin 4 | http://eblong.com/zarf/glk/ 5 | */ 6 | 7 | #include 8 | #include 9 | #include 10 | #include /* for unlink() */ 11 | #include /* for stat() */ 12 | 13 | #include "glk.h" 14 | #include "gi_dispa.h" 15 | #include "remglk.h" 16 | #include "rgdata.h" 17 | 18 | /* This code implements filerefs as they work in a stdio system: a 19 | fileref contains a pathname, a text/binary flag, and a file 20 | type. 21 | */ 22 | 23 | /* Used to generate fileref updatetag values. */ 24 | static glui32 tagcounter = 0; 25 | 26 | /* Linked list of all filerefs */ 27 | static fileref_t *gli_filereflist = NULL; 28 | 29 | /* The directory used for by_name files, and as the base for by_prompt 30 | files. Defaults to ".". */ 31 | static char *workingdir = NULL; 32 | 33 | void gli_initialize_filerefs() 34 | { 35 | tagcounter = (random() % 15) + 48; 36 | 37 | if (!workingdir) 38 | gli_fileref_set_working_dir("."); 39 | } 40 | 41 | fileref_t *glkunix_fileref_find_by_updatetag(glui32 tag) 42 | { 43 | fileref_t *fref; 44 | for (fref=gli_filereflist; fref; fref=fref->next) { 45 | if (fref->updatetag == tag) 46 | return fref; 47 | } 48 | return NULL; 49 | } 50 | 51 | void glkunix_fileref_set_dispatch_rock(frefid_t fref, gidispatch_rock_t rock) 52 | { 53 | fref->disprock = rock; 54 | } 55 | 56 | glui32 glkunix_fileref_get_updatetag(frefid_t fref) 57 | { 58 | return fref->updatetag; 59 | } 60 | 61 | fileref_t *gli_new_fileref(char *filename, glui32 usage, glui32 rock) 62 | { 63 | fileref_t *fref = (fileref_t *)malloc(sizeof(fileref_t)); 64 | if (!fref) 65 | return NULL; 66 | 67 | fref->magicnum = MAGIC_FILEREF_NUM; 68 | fref->rock = rock; 69 | fref->updatetag = tagcounter; 70 | tagcounter += 7; 71 | 72 | fref->filename = malloc(1 + strlen(filename)); 73 | strcpy(fref->filename, filename); 74 | 75 | fref->textmode = ((usage & fileusage_TextMode) != 0); 76 | fref->filetype = (usage & fileusage_TypeMask); 77 | 78 | fref->prev = NULL; 79 | fref->next = gli_filereflist; 80 | gli_filereflist = fref; 81 | if (fref->next) { 82 | fref->next->prev = fref; 83 | } 84 | 85 | if (gli_register_obj) 86 | fref->disprock = (*gli_register_obj)(fref, gidisp_Class_Fileref); 87 | 88 | return fref; 89 | } 90 | 91 | fileref_t *gli_fileref_alloc_inactive() 92 | { 93 | fileref_t *fref = (fileref_t *)malloc(sizeof(fileref_t)); 94 | if (!fref) 95 | return NULL; 96 | 97 | fref->magicnum = MAGIC_FILEREF_NUM; 98 | fref->rock = 0; 99 | fref->updatetag = 0; 100 | 101 | fref->filename = NULL; 102 | 103 | fref->textmode = 0; 104 | fref->filetype = 0; 105 | 106 | fref->prev = NULL; 107 | fref->next = NULL; 108 | 109 | return fref; 110 | } 111 | 112 | void gli_fileref_dealloc_inactive(fileref_t *fref) 113 | { 114 | if (fref->filename) { 115 | free(fref->filename); 116 | fref->filename = NULL; 117 | } 118 | 119 | free(fref); 120 | } 121 | 122 | void gli_delete_fileref(fileref_t *fref) 123 | { 124 | fileref_t *prev, *next; 125 | 126 | if (gli_unregister_obj) 127 | (*gli_unregister_obj)(fref, gidisp_Class_Fileref, fref->disprock); 128 | 129 | fref->magicnum = 0; 130 | 131 | if (fref->filename) { 132 | free(fref->filename); 133 | fref->filename = NULL; 134 | } 135 | 136 | prev = fref->prev; 137 | next = fref->next; 138 | fref->prev = NULL; 139 | fref->next = NULL; 140 | 141 | if (prev) 142 | prev->next = next; 143 | else 144 | gli_filereflist = next; 145 | if (next) 146 | next->prev = prev; 147 | 148 | free(fref); 149 | } 150 | 151 | int gli_filerefs_update_from_state(fileref_t **list, int count) 152 | { 153 | if (gli_filereflist) { 154 | gli_fatal_error("filerefs already exist"); 155 | return FALSE; 156 | } 157 | 158 | int ix; 159 | for (ix=count-1; ix>=0; ix--) { 160 | frefid_t fref = list[ix]; 161 | fref->next = gli_filereflist; 162 | gli_filereflist = fref; 163 | if (fref->next) { 164 | fref->next->prev = fref; 165 | } 166 | 167 | if (fref->updatetag >= tagcounter) 168 | tagcounter = fref->updatetag + 7; 169 | } 170 | 171 | return TRUE; 172 | } 173 | 174 | void glk_fileref_destroy(fileref_t *fref) 175 | { 176 | if (!fref) { 177 | gli_strict_warning("fileref_destroy: invalid ref"); 178 | return; 179 | } 180 | gli_delete_fileref(fref); 181 | } 182 | 183 | #define MAX_SUFFIX_LENGTH (8) 184 | 185 | static char *gli_suffix_for_usage(glui32 usage) 186 | { 187 | switch (usage & fileusage_TypeMask) { 188 | case fileusage_Data: 189 | return ".glkdata"; 190 | case fileusage_SavedGame: 191 | return ".glksave"; 192 | case fileusage_Transcript: 193 | case fileusage_InputRecord: 194 | return ".txt"; 195 | default: 196 | return ""; 197 | } 198 | } 199 | 200 | frefid_t glk_fileref_create_temp(glui32 usage, glui32 rock) 201 | { 202 | char filename[256]; 203 | fileref_t *fref; 204 | 205 | sprintf(filename, "/tmp/glktempfref-XXXXXX"); 206 | close(mkstemp(filename)); 207 | 208 | fref = gli_new_fileref(filename, usage, rock); 209 | if (!fref) { 210 | gli_strict_warning("fileref_create_temp: unable to create fileref."); 211 | return NULL; 212 | } 213 | 214 | return fref; 215 | } 216 | 217 | frefid_t glk_fileref_create_from_fileref(glui32 usage, frefid_t oldfref, 218 | glui32 rock) 219 | { 220 | fileref_t *fref; 221 | 222 | if (!oldfref) { 223 | gli_strict_warning("fileref_create_from_fileref: invalid ref"); 224 | return NULL; 225 | } 226 | 227 | fref = gli_new_fileref(oldfref->filename, usage, rock); 228 | if (!fref) { 229 | gli_strict_warning("fileref_create_from_fileref: unable to create fileref."); 230 | return NULL; 231 | } 232 | 233 | return fref; 234 | } 235 | 236 | frefid_t glk_fileref_create_by_name(glui32 usage, char *name, 237 | glui32 rock) 238 | { 239 | fileref_t *fref; 240 | char *buf; 241 | char *newbuf; 242 | int len; 243 | char *cx; 244 | char *suffix; 245 | 246 | /* The new spec recommendations: delete all characters in the 247 | string "/\<>:|?*" (including quotes). Truncate at the first 248 | period. Change to "null" if there's nothing left. Then append 249 | an appropriate suffix: ".glkdata", ".glksave", ".txt". 250 | */ 251 | 252 | buf = malloc(strlen(name) + 8); 253 | 254 | for (cx=name, len=0; (*cx && *cx!='.'); cx++) { 255 | switch (*cx) { 256 | case '"': 257 | case '\\': 258 | case '/': 259 | case '>': 260 | case '<': 261 | case ':': 262 | case '|': 263 | case '?': 264 | case '*': 265 | break; 266 | default: 267 | buf[len++] = *cx; 268 | } 269 | } 270 | buf[len] = '\0'; 271 | 272 | if (len == 0 || !strcmp(buf, ".") || !strcmp(buf, "..")) { 273 | strcpy(buf, "null"); 274 | len = strlen(buf); 275 | } 276 | 277 | suffix = gli_suffix_for_usage(usage); 278 | newbuf = malloc(strlen(workingdir) + 1 + strlen(buf) + strlen(suffix) + 4); 279 | 280 | sprintf(newbuf, "%s/%s%s", workingdir, buf, suffix); 281 | 282 | free(buf); 283 | buf = NULL; 284 | 285 | fref = gli_new_fileref(newbuf, usage, rock); 286 | if (!fref) { 287 | gli_strict_warning("fileref_create_by_name: unable to create fileref."); 288 | free(newbuf); 289 | return NULL; 290 | } 291 | 292 | free(newbuf); 293 | return fref; 294 | } 295 | 296 | frefid_t glk_fileref_create_by_prompt(glui32 usage, glui32 fmode, 297 | glui32 rock) 298 | { 299 | fileref_t *fref; 300 | char *buf; 301 | char *newbuf; 302 | char *cx; 303 | int val, gotdot; 304 | 305 | /* Set up special request. */ 306 | data_specialreq_t *special = data_specialreq_alloc(fmode, (usage & fileusage_TypeMask)); 307 | special->gameid = NULL; 308 | 309 | #ifdef GI_DISPA_GAME_ID_AVAILABLE 310 | cx = gidispatch_get_game_id(); 311 | if (cx) { 312 | special->gameid = strdup(cx); 313 | } 314 | #endif /* GI_DISPA_GAME_ID_AVAILABLE */ 315 | 316 | /* This will look a lot like glk_select(), but we're waiting only for 317 | a special-input response. */ 318 | buf = gli_select_specialrequest(special); 319 | 320 | if (!buf) { 321 | /* The player cancelled input. */ 322 | return NULL; 323 | } 324 | 325 | /* Trim whitespace from end and beginning. */ 326 | val = strlen(buf); 327 | while (val 328 | && (buf[val-1] == '\n' 329 | || buf[val-1] == '\r' 330 | || buf[val-1] == ' ')) 331 | val--; 332 | buf[val] = '\0'; 333 | 334 | for (cx = buf; *cx == ' '; cx++) { } 335 | 336 | val = strlen(cx); 337 | if (!val) { 338 | /* The player just hit return. */ 339 | free(buf); 340 | return NULL; 341 | } 342 | 343 | if (pref_onlyfiledir) { 344 | /* The player is restricted to workingdir. We will drop slashes 345 | from the filename, and truncate at the first period. (But we 346 | permit other punctuation.) */ 347 | newbuf = malloc(val + strlen(workingdir) + 8 + MAX_SUFFIX_LENGTH); 348 | int len; 349 | 350 | sprintf(newbuf, "%s/", workingdir); 351 | char *buf2 = newbuf + strlen(newbuf); 352 | 353 | /* Ugly even for C code... buf2 is the buffer segment after 354 | workingdir, which is guaranteed to have 355 | strlen(cx)+8+MAX_SUFFIX_LENGTH available space. */ 356 | 357 | for (len=0; (*cx && *cx!='.'); cx++) { 358 | switch (*cx) { 359 | case '\\': 360 | case '/': 361 | break; 362 | default: 363 | buf2[len++] = *cx; 364 | } 365 | } 366 | buf2[len] = '\0'; 367 | 368 | if (len == 0 || !strcmp(buf2, ".") || !strcmp(buf2, "..")) { 369 | strcpy(buf2, "null"); 370 | } 371 | } 372 | else { 373 | /* The player is allowed to pick a relative or absolute directory. */ 374 | newbuf = malloc(val + strlen(workingdir) + 4 + MAX_SUFFIX_LENGTH); 375 | 376 | if (cx[0] == '/') 377 | strcpy(newbuf, cx); 378 | else 379 | sprintf(newbuf, "%s/%s", workingdir, cx); 380 | } 381 | 382 | free(buf); 383 | buf = NULL; 384 | cx = NULL; 385 | 386 | /* If there is no dot-suffix, add a standard one. */ 387 | val = strlen(newbuf); 388 | gotdot = FALSE; 389 | while (val && (newbuf[val-1] != '/')) { 390 | if (newbuf[val-1] == '.') { 391 | gotdot = TRUE; 392 | break; 393 | } 394 | val--; 395 | } 396 | if (!gotdot) { 397 | char *suffix = gli_suffix_for_usage(usage); 398 | strcat(newbuf, suffix); 399 | } 400 | 401 | /* We don't do an overwrite check, because that would be another 402 | interchange. */ 403 | 404 | if (fmode == filemode_Read) { 405 | /* According to recent spec discussion, we must silently return NULL if no such file exists. */ 406 | if (access(newbuf, R_OK)) { 407 | free(newbuf); 408 | return NULL; 409 | } 410 | } 411 | 412 | fref = gli_new_fileref(newbuf, usage, rock); 413 | if (!fref) { 414 | gli_strict_warning("fileref_create_by_prompt: unable to create fileref."); 415 | free(newbuf); 416 | return NULL; 417 | } 418 | 419 | free(newbuf); 420 | return fref; 421 | } 422 | 423 | frefid_t glk_fileref_iterate(fileref_t *fref, glui32 *rock) 424 | { 425 | if (!fref) { 426 | fref = gli_filereflist; 427 | } 428 | else { 429 | fref = fref->next; 430 | } 431 | 432 | if (fref) { 433 | if (rock) 434 | *rock = fref->rock; 435 | return fref; 436 | } 437 | 438 | if (rock) 439 | *rock = 0; 440 | return NULL; 441 | } 442 | 443 | glui32 glk_fileref_get_rock(fileref_t *fref) 444 | { 445 | if (!fref) { 446 | gli_strict_warning("fileref_get_rock: invalid ref."); 447 | return 0; 448 | } 449 | 450 | return fref->rock; 451 | } 452 | 453 | glui32 glk_fileref_does_file_exist(fileref_t *fref) 454 | { 455 | struct stat statbuf; 456 | 457 | if (!fref) { 458 | gli_strict_warning("fileref_does_file_exist: invalid ref"); 459 | return FALSE; 460 | } 461 | 462 | /* This is sort of Unix-specific, but probably any stdio library 463 | will implement at least this much of stat(). */ 464 | 465 | if (stat(fref->filename, &statbuf)) 466 | return 0; 467 | 468 | if (S_ISREG(statbuf.st_mode)) 469 | return 1; 470 | else 471 | return 0; 472 | } 473 | 474 | void glk_fileref_delete_file(fileref_t *fref) 475 | { 476 | if (!fref) { 477 | gli_strict_warning("fileref_delete_file: invalid ref"); 478 | return; 479 | } 480 | 481 | /* If you don't have the unlink() function, obviously, change it 482 | to whatever file-deletion function you do have. */ 483 | 484 | unlink(fref->filename); 485 | } 486 | 487 | /* Set the working directory, based on the directory of the given filename 488 | (or the directory itself, if it is one). 489 | This should only be called from init or startup code. */ 490 | void gli_fileref_set_working_dir(char *filename) 491 | { 492 | int ix; 493 | 494 | if (!strcmp(filename, ".")) { 495 | /* Process cwd is always valid. */ 496 | if (workingdir) 497 | free(workingdir); 498 | workingdir = strdup("."); 499 | return; 500 | } 501 | 502 | struct stat statbuf; 503 | if (stat(filename, &statbuf)) 504 | return; 505 | 506 | if (S_ISDIR(statbuf.st_mode)) { 507 | /* Use the directory name. */ 508 | if (workingdir) 509 | free(workingdir); 510 | workingdir = strdup(filename); 511 | return; 512 | } 513 | 514 | /* Peel off the filename and use the directory part. */ 515 | 516 | for (ix=strlen(filename)-1; ix >= 0; ix--) 517 | if (filename[ix] == '/') 518 | break; 519 | 520 | if (ix >= 0) { 521 | /* There is a slash. Copy up to there. */ 522 | if (workingdir) 523 | free(workingdir); 524 | workingdir = malloc(ix+4); 525 | strncpy(workingdir, filename, ix); 526 | workingdir[ix] = '\0'; 527 | } 528 | else { 529 | /* No slash, just a filename. Use ".". */ 530 | if (workingdir) 531 | free(workingdir); 532 | workingdir = strdup("."); 533 | } 534 | } 535 | 536 | /* This is called when the interpreter learns the game file pathname 537 | (assuming there is one). 538 | This should only be called from startup code. */ 539 | void glkunix_set_base_file(char *filename) 540 | { 541 | if (pref_gamefiledir) 542 | gli_fileref_set_working_dir(filename); 543 | } 544 | 545 | /* The emglken interpreters need to reach in and get this info. They 546 | were built for the garglk port, which has an accessor called 547 | garglk_fileref_get_name(). 548 | (Note that the garglk codebase uses "const", even though remglk 549 | generally doesn't.) */ 550 | const char *glkunix_fileref_get_filename(frefid_t fref) 551 | { 552 | return fref->filename; 553 | } 554 | 555 | -------------------------------------------------------------------------------- /rgwin_grid.c: -------------------------------------------------------------------------------- 1 | /* rgwin_grid.c: The grid window type 2 | for RemGlk, remote-procedure-call implementation of the Glk API. 3 | Designed by Andrew Plotkin 4 | http://eblong.com/zarf/glk/ 5 | */ 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | #include "glk.h" 12 | #include "remglk.h" 13 | #include "rgdata.h" 14 | #include "rgwin_grid.h" 15 | 16 | /* A grid of characters. We store the window as a list of lines (see 17 | gtwgrid.h); within a line, just store an array of characters and 18 | an array of style bytes, the same size. (If we ever have more than 19 | 255 styles, things will have to be changed, but that's unlikely.) 20 | */ 21 | 22 | 23 | window_textgrid_t *win_textgrid_create(window_t *win) 24 | { 25 | window_textgrid_t *dwin = (window_textgrid_t *)malloc(sizeof(window_textgrid_t)); 26 | dwin->owner = win; 27 | 28 | dwin->width = 0; 29 | dwin->height = 0; 30 | 31 | dwin->curx = 0; 32 | dwin->cury = 0; 33 | 34 | dwin->linessize = 0; 35 | dwin->lines = NULL; 36 | 37 | dwin->inbuf = NULL; 38 | dwin->incurpos = 0; 39 | dwin->inunicode = FALSE; 40 | dwin->inecho = FALSE; 41 | dwin->intermkeys = 0; 42 | dwin->inoriglen = 0; 43 | dwin->inmax = 0; 44 | dwin->origstyle = 0; 45 | 46 | dwin->inarrayrock.num = 0; 47 | 48 | return dwin; 49 | } 50 | 51 | void win_textgrid_destroy(window_textgrid_t *dwin) 52 | { 53 | if (dwin->inbuf) { 54 | if (gli_unregister_arr) { 55 | char *typedesc = (dwin->inunicode ? "&+#!Iu" : "&+#!Cn"); 56 | (*gli_unregister_arr)(dwin->inbuf, dwin->inoriglen, typedesc, dwin->inarrayrock); 57 | } 58 | dwin->inbuf = NULL; 59 | } 60 | 61 | dwin->owner = NULL; 62 | if (dwin->lines) { 63 | int jx; 64 | for (jx=0; jxlinessize; jx++) { 65 | tgline_t *ln = &(dwin->lines[jx]); 66 | if (ln->chars) { 67 | free(ln->chars); 68 | ln->chars = NULL; 69 | } 70 | if (ln->styles) { 71 | free(ln->styles); 72 | ln->styles = NULL; 73 | } 74 | if (ln->links) { 75 | free(ln->links); 76 | ln->links = NULL; 77 | } 78 | } 79 | 80 | free(dwin->lines); 81 | dwin->lines = NULL; 82 | } 83 | free(dwin); 84 | } 85 | 86 | void win_textgrid_rearrange(window_t *win, grect_t *box, data_metrics_t *metrics) 87 | { 88 | int ix, jx, oldval; 89 | int newwid, newhgt; 90 | window_textgrid_t *dwin = win->data; 91 | dwin->owner->bbox = *box; 92 | 93 | newwid = (int) floor(((box->right - box->left) - metrics->gridmarginx) / metrics->gridcharwidth); 94 | newhgt = (int) floor(((box->bottom - box->top) - metrics->gridmarginy) / metrics->gridcharheight); 95 | 96 | if (dwin->lines == NULL) { 97 | dwin->linessize = (newhgt+1); 98 | dwin->lines = (tgline_t *)malloc(dwin->linessize * sizeof(tgline_t)); 99 | if (!dwin->lines) 100 | return; 101 | win_textgrid_alloc_lines(dwin, 0, dwin->linessize, newwid); 102 | } 103 | else { 104 | if (newhgt > dwin->linessize) { 105 | oldval = dwin->linessize; 106 | dwin->linessize = (newhgt+1) * 2; 107 | dwin->lines = (tgline_t *)realloc(dwin->lines, 108 | dwin->linessize * sizeof(tgline_t)); 109 | if (!dwin->lines) 110 | return; 111 | win_textgrid_alloc_lines(dwin, oldval, dwin->linessize, newwid); 112 | } 113 | if (newhgt > dwin->height) { 114 | /* Clear any new lines */ 115 | for (jx=dwin->height; jxlines[jx]); 117 | for (ix=0; ixallocsize; ix++) { 118 | ln->chars[ix] = ' '; 119 | ln->styles[ix] = style_Normal; 120 | ln->links[ix] = 0; 121 | } 122 | } 123 | } 124 | for (jx=0; jxlines[jx]); 126 | if (newwid > ln->allocsize) { 127 | oldval = ln->allocsize; 128 | ln->allocsize = (newwid+1) * 2; 129 | ln->chars = (glui32 *)realloc(ln->chars, 130 | ln->allocsize * sizeof(glui32)); 131 | ln->styles = (short *)realloc(ln->styles, 132 | ln->allocsize * sizeof(short)); 133 | ln->links = (glui32 *)realloc(ln->links, 134 | ln->allocsize * sizeof(glui32)); 135 | if (!ln->chars || !ln->styles) { 136 | dwin->lines = NULL; 137 | return; 138 | } 139 | for (ix=oldval; ixallocsize; ix++) { 140 | ln->chars[ix] = ' '; 141 | ln->styles[ix] = style_Normal; 142 | ln->links[ix] = 0; 143 | } 144 | } 145 | } 146 | } 147 | 148 | dwin->width = newwid; 149 | dwin->height = newhgt; 150 | 151 | dwin->alldirty = TRUE; 152 | } 153 | 154 | void win_textgrid_alloc_lines(window_textgrid_t *dwin, int beg, int end, int linewid) 155 | { 156 | int ix, jx; 157 | 158 | for (jx=beg; jxlines[jx]); 160 | ln->allocsize = (linewid+1); 161 | ln->dirty = TRUE; 162 | ln->chars = (glui32 *)malloc(ln->allocsize * sizeof(glui32)); 163 | ln->styles = (short *)malloc(ln->allocsize * sizeof(short)); 164 | ln->links = (glui32 *)malloc(ln->allocsize * sizeof(glui32)); 165 | if (!ln->chars || !ln->styles || !ln->links) { 166 | dwin->lines = NULL; 167 | return; 168 | } 169 | for (ix=0; ixallocsize; ix++) { 170 | ln->chars[ix] = ' '; 171 | ln->styles[ix] = style_Normal; 172 | ln->links[ix] = 0; 173 | } 174 | } 175 | } 176 | 177 | data_content_t *win_textgrid_update(window_t *win) 178 | { 179 | int jx, ix; 180 | int spanstart; 181 | short curstyle; 182 | glui32 curlink; 183 | window_textgrid_t *dwin = win->data; 184 | 185 | if (!dwin->lines) { 186 | dwin->alldirty = FALSE; 187 | return NULL; 188 | } 189 | 190 | data_content_t *dat = NULL; 191 | 192 | for (jx=0; jxheight; jx++) { 193 | tgline_t *ln = &(dwin->lines[jx]); 194 | if (!dwin->alldirty && !ln->dirty) 195 | continue; 196 | 197 | if (!dat) 198 | dat = data_content_alloc(win->updatetag, win->type); 199 | 200 | data_line_t *line = data_line_alloc(); 201 | gen_list_append(&dat->lines, line); 202 | line->linenum = jx; 203 | 204 | curstyle = -1; 205 | curlink = 0; 206 | spanstart = 0; 207 | for (ix=0; ixwidth; ix++) { 208 | if (ln->styles[ix] != curstyle || ln->links[ix] != curlink) { 209 | if (ix > spanstart) { 210 | data_line_add_span(line, curstyle, curlink, ln->chars+spanstart, ix-spanstart); 211 | spanstart = ix; 212 | } 213 | 214 | curstyle = ln->styles[ix]; 215 | curlink = ln->links[ix]; 216 | } 217 | } 218 | if (ix > spanstart) { 219 | data_line_add_span(line, curstyle, curlink, ln->chars+spanstart, ix-spanstart); 220 | spanstart = ix; 221 | } 222 | 223 | ln->dirty = FALSE; 224 | } 225 | 226 | dwin->alldirty = FALSE; 227 | 228 | return dat; 229 | } 230 | 231 | void win_textgrid_putchar(window_t *win, glui32 ch) 232 | { 233 | window_textgrid_t *dwin = win->data; 234 | tgline_t *ln; 235 | 236 | /* Canonicalize the cursor position. That is, the cursor may have been 237 | left outside the window area; wrap it if necessary. */ 238 | if (dwin->curx < 0) 239 | dwin->curx = 0; 240 | else if (dwin->curx >= dwin->width) { 241 | dwin->curx = 0; 242 | dwin->cury++; 243 | } 244 | if (dwin->cury < 0) 245 | dwin->cury = 0; 246 | else if (dwin->cury >= dwin->height) 247 | return; /* outside the window */ 248 | 249 | if (ch == '\n') { 250 | /* a newline just moves the cursor. */ 251 | dwin->cury++; 252 | dwin->curx = 0; 253 | return; 254 | } 255 | 256 | ln = &(dwin->lines[dwin->cury]); 257 | ln->dirty = TRUE; 258 | 259 | ln->chars[dwin->curx] = ch; 260 | ln->styles[dwin->curx] = win->style; 261 | ln->links[dwin->curx] = win->hyperlink; 262 | 263 | dwin->curx++; 264 | /* We can leave the cursor outside the window, since it will be 265 | canonicalized next time a character is printed. */ 266 | } 267 | 268 | void win_textgrid_clear(window_t *win) 269 | { 270 | int ix, jx; 271 | window_textgrid_t *dwin = win->data; 272 | 273 | for (jx=0; jxheight; jx++) { 274 | tgline_t *ln = &(dwin->lines[jx]); 275 | for (ix=0; ixwidth; ix++) { 276 | ln->chars[ix] = ' '; 277 | ln->styles[ix] = style_Normal; 278 | ln->links[ix] = 0; 279 | } 280 | ln->dirty = TRUE; 281 | } 282 | 283 | dwin->alldirty = TRUE; 284 | 285 | dwin->curx = 0; 286 | dwin->cury = 0; 287 | } 288 | 289 | void win_textgrid_move_cursor(window_t *win, int xpos, int ypos) 290 | { 291 | window_textgrid_t *dwin = win->data; 292 | 293 | /* If the values are negative, they're really huge positive numbers -- 294 | remember that they were cast from glui32. So set them huge and 295 | let canonicalization take its course. */ 296 | if (xpos < 0) 297 | xpos = 32767; 298 | if (ypos < 0) 299 | ypos = 32767; 300 | 301 | dwin->curx = xpos; 302 | dwin->cury = ypos; 303 | } 304 | 305 | /* Prepare the window for line input. */ 306 | void win_textgrid_init_line(window_t *win, void *buf, int unicode, 307 | int maxlen, int initlen) 308 | { 309 | window_textgrid_t *dwin = win->data; 310 | 311 | /* Canonicalize the cursor position a little. */ 312 | if (dwin->curx >= dwin->width) { 313 | dwin->curx = 0; 314 | dwin->cury++; 315 | } 316 | if (dwin->cury >= dwin->height) { 317 | /* Outside the window; put the cursor in the bottom right. */ 318 | dwin->curx = dwin->width-1; 319 | dwin->cury = dwin->height-1; 320 | } 321 | 322 | dwin->inbuf = buf; 323 | dwin->inunicode = unicode; 324 | dwin->inoriglen = maxlen; 325 | if (maxlen > (dwin->width - dwin->curx)) 326 | maxlen = (dwin->width - dwin->curx); 327 | dwin->inmax = maxlen; 328 | dwin->inecho = win->echo_line_input; 329 | dwin->intermkeys = win->terminate_line_input; 330 | dwin->origstyle = win->style; 331 | win->style = style_Input; 332 | 333 | if (initlen > maxlen) 334 | initlen = maxlen; 335 | 336 | if (gli_register_arr) { 337 | char *typedesc = (dwin->inunicode ? "&+#!Iu" : "&+#!Cn"); 338 | dwin->inarrayrock = (*gli_register_arr)(dwin->inbuf, dwin->inoriglen, typedesc); 339 | } 340 | } 341 | 342 | void win_textgrid_prepare_input(window_t *win, glui32 *buf, glui32 len) 343 | { 344 | window_textgrid_t *dwin = win->data; 345 | int ix; 346 | 347 | if (!dwin->inbuf) 348 | return; 349 | 350 | if (len > dwin->inmax) 351 | len = dwin->inmax; 352 | 353 | dwin->incurpos = len; 354 | 355 | if (!dwin->inunicode) { 356 | char *inbuf = ((char *)dwin->inbuf); 357 | for (ix=0; ix= 0 && ch < 256)) 360 | ch = '?'; 361 | inbuf[ix] = ch; 362 | } 363 | } 364 | else { 365 | glui32 *inbuf = ((glui32 *)dwin->inbuf); 366 | for (ix=0; ixdata; 380 | 381 | if (!dwin->inbuf) 382 | return; 383 | 384 | inbuf = dwin->inbuf; 385 | inmax = dwin->inmax; 386 | inoriglen = dwin->inoriglen; 387 | inarrayrock = dwin->inarrayrock; 388 | inunicode = dwin->inunicode; 389 | inecho = dwin->inecho; 390 | 391 | len = dwin->incurpos; 392 | if (inecho && win->echostr) { 393 | if (!inunicode) 394 | gli_stream_echo_line(win->echostr, (char *)inbuf, len); 395 | else 396 | gli_stream_echo_line_uni(win->echostr, (glui32 *)inbuf, len); 397 | } 398 | 399 | if (inecho) { 400 | /* Add the typed text to the grid. */ 401 | int ix; 402 | if (!inunicode) { 403 | for (ix=0; ixcury = dwin->cury+1; 416 | dwin->curx = 0; 417 | } 418 | 419 | win->style = dwin->origstyle; 420 | 421 | /* ### set termkey */ 422 | 423 | gli_event_store(evtype_LineInput, win, len, termkey); 424 | win->line_request = FALSE; 425 | dwin->inbuf = NULL; 426 | dwin->inoriglen = 0; 427 | dwin->incurpos = 0; 428 | dwin->inmax = 0; 429 | dwin->inecho = FALSE; 430 | dwin->intermkeys = 0; 431 | 432 | if (gli_unregister_arr) { 433 | char *typedesc = (inunicode ? "&+#!Iu" : "&+#!Cn"); 434 | (*gli_unregister_arr)(inbuf, inoriglen, typedesc, inarrayrock); 435 | } 436 | } 437 | 438 | /* Abort line input, storing whatever's been typed so far. */ 439 | void win_textgrid_cancel_line(window_t *win, event_t *ev) 440 | { 441 | void *inbuf; 442 | int inoriglen, inmax, inunicode, inecho, len; 443 | gidispatch_rock_t inarrayrock; 444 | window_textgrid_t *dwin = win->data; 445 | 446 | if (!dwin->inbuf) 447 | return; 448 | 449 | inbuf = dwin->inbuf; 450 | inmax = dwin->inmax; 451 | inoriglen = dwin->inoriglen; 452 | inarrayrock = dwin->inarrayrock; 453 | inunicode = dwin->inunicode; 454 | inecho = dwin->inecho; 455 | 456 | len = dwin->incurpos; 457 | if (inecho && win->echostr) { 458 | if (!inunicode) 459 | gli_stream_echo_line(win->echostr, (char *)inbuf, len); 460 | else 461 | gli_stream_echo_line_uni(win->echostr, (glui32 *)inbuf, len); 462 | } 463 | 464 | if (inecho) { 465 | /* Add the typed text to the buffer. */ 466 | int ix; 467 | if (!inunicode) { 468 | for (ix=0; ixcury = dwin->cury+1; 481 | dwin->curx = 0; 482 | } 483 | 484 | win->style = dwin->origstyle; 485 | 486 | ev->type = evtype_LineInput; 487 | ev->win = win; 488 | ev->val1 = len; 489 | 490 | win->line_request = FALSE; 491 | dwin->inbuf = NULL; 492 | dwin->inoriglen = 0; 493 | dwin->inmax = 0; 494 | dwin->intermkeys = 0; 495 | 496 | if (gli_unregister_arr) { 497 | char *typedesc = (inunicode ? "&+#!Iu" : "&+#!Cn"); 498 | (*gli_unregister_arr)(inbuf, inoriglen, typedesc, inarrayrock); 499 | } 500 | } 501 | -------------------------------------------------------------------------------- /docs.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | RemGlk: remote-procedure-call implementation of the Glk IF API 5 | 6 | 104 | 105 | 106 | 107 | 108 |

RemGlk: remote-procedure-call implementation of the Glk IF API

109 | 110 | RemGlk version 0.3.2
111 | 112 | Designed by Andrew Plotkin (erkyrath@eblong.com)
113 | (Glk home page)
114 |

115 | 116 | The RemGlk library is copyright 2012-2025 by Andrew Plotkin. The 117 | GiDispa, and GiBlorb libraries, as well as the glk.h header file, are 118 | copyright 1998-2025 by Andrew Plotkin. They are distributed under the 119 | MIT license. 120 |

121 | 122 | This documentation is licensed under a 123 | Creative 124 | Commons Attribution-Noncommercial-ShareAlike 4.0 International License. 125 |

126 | 127 |


128 | 129 |

What is RemGlk?

130 | 131 | RemGlk is a C library which implements the Glk API. You can compile a Glk application and link it with this library. 132 |

133 | 134 | RemGlk does not provide a user interface. Instead, it wraps up the application's output as a JSON data structure and sends it to stdout. It then waits for input to arrive from stdin; the input data must also be encoded as JSON. 135 |

136 | 137 | RemGlk is therefore like CheapGlk, in that it works entirely through input and output streams, and can easily be attached to a bot or web service. However, unlike CheapGlk, RemGlk supports multiple Glk windows and most Glk I/O features. Whatever it's attached to just has to decode the structured output and display it appropriately. 138 |

139 | 140 | RemGlk was designed to complement the GlkOte Javascript library. GlkOte more or less implements the other half of the job: it accepts game output (as Javascript objects), displays it, and then returns the player's commands in the same format. It is possible to connect an IF interpreter, RemGlk, and GlkOte to build a complete IF web service. (See an experimental implementation of this idea.) Since RemGlk supports autosave/autorestore, it is practical to create a service which fires up a RemGlk process momentarily to process each player command. 141 |

142 | 143 | RemGlk is also used by the RegTest testing framework. In this setup, the "display layer" does not actually display anything. It is a Python script which generates test input and verifies the game's output. 144 |

145 | 146 |

Using RemGlk

147 | 148 |

Starting up

149 | 150 | Once you have compiled your application, run it as usual. For glulxe, this probably looks like: 151 |

152 | 153 |

154 | glulxe Advent.ulx
155 | 
156 |

157 | 158 | If you do this, you will see nothing at first. The application is waiting for an initial input which describes the display and font sizes. Try typing this, exactly: 159 |

160 | 161 |

162 | { "type": "init", "gen": 0, "metrics": { "width":80, "height":24 } }
163 | 
164 |

165 | 166 | This tells RemGlk that it has an 80x24 "screen" to work with. The default "font size" is 1, so this gives the effect of an 80x24 terminal window. 167 |

168 | 169 | If you instead entered: 170 | 171 |

172 | { "type": "init", "gen": 0, "metrics": { "width":400, "height":600,
173 |   "charwidth":12, "charheight":14 } }
174 | 
175 |

176 | 177 | ...you would get a layout for a 400x600 pixel window, assuming a 14-point font with 12-pixel-wide characters. 178 |

179 | 180 | (The font size is only critical for grid (status) windows. You can approximate the measurements for buffer (story) windows; these typically have proportional fonts and perhaps various font sizes as well.) 181 |

182 | 183 | The metrics object also allows you to specify margins and border spacing, which improve the layout behavior. See below for details. 184 |

185 | 186 | You can also include information about what optional features you are prepared to support. (RemGlk needs to know this in order to set appropriate gestalt flags for the game.) 187 |

188 | 189 |

190 | { "type": "init", "gen": 0, "metrics": { "width":80, "height":24 },
191 |   "support": [ "timer", "hyperlinks" ] }
192 | 
193 |

194 | 195 | The support field is a list of strings. Currently the recognized values are: 196 |

197 | 198 |

    199 |
  • "timer": gestalt_Timer will be set; the library will support glk_request_timer_events(). 200 |
  • "hyperlinks": gestalt_Hyperlinks and gestalt_HyperlinkInput will be set; the library will support glk_set_hyperlink() and glk_request_hyperlink_event(). 201 |
  • "graphics": gestalt_Graphics and gestalt_GraphicsTransparency will be set. (Support for transparent PNGs is taken for granted.) gestalt_DrawImage will return true for text-buffer windows. The library will support all image functions (except glk_image_draw_scaled_ext(); see below). 202 |
  • "graphicswin": graphics windows can be opened; gestalt_DrawImage will return true for graphics windows. 203 |
  • "graphicsext": The library will support glk_image_draw_scaled_ext(). gestalt_DrawImageScale will return true for buffer windows and (with "graphicswin") graphics windows as well. 204 |
205 | 206 |

Starting up with no init event

207 | 208 | For some scenarios, often including debugging, you want the application to not wait for an init event. You can request this with the -fixmetrics (or -fm) flag. You can also specify basic layout information on the command line. If you type: 209 | 210 |
211 | glulxe -fm -width 80 -height 50 Advent.ulx
212 | 
213 |

214 | 215 | ...then the game will start immediately (with an 80x50 "terminal window"), and send its first screenful of output. Your first reply will then be a player command; see below. 216 |

217 | 218 | In -fm mode, you must specify optional features on the command line. Use the -support argument with one of the support strings listed above (-support timer, -support hyperlinks, etc.). 219 |

220 | 221 |

The first screen of output

222 | 223 | For Adventure, the initial output looks something like: 224 | 225 |
226 | {"type":"update", "gen":1,
227 |  "windows":[
228 |  { "id":25, "type":"grid", "rock":202,
229 |    "gridwidth":80, "gridheight":1,
230 |    "left":0, "top":0, "width":80, "height":1 },
231 |  { "id":22, "type":"buffer", "rock":201,
232 |    "left":0, "top":1, "width":80, "height":49 } ],
233 |  "content":[
234 |  {"id":25, "lines": [
235 |   { "line":0, "content":[{ "style":"normal", "text":" At End Of Road
236 |      Score: 36    Moves: 1      "}]}
237 |  ] },
238 |  {"id":22, "clear":true, "text": [
239 |   {"append":true},
240 |   {}, {}, {}, {}, {},
241 |   {"content":[{ "style":"normal", "text":"Welcome to Adventure!"}]},
242 |   {},
243 |   {"content":[{ "style":"header", "text":"ADVENTURE"}]},
244 |   {"content":[{ "style":"normal", "text":"The Interactive Original"}]},
245 |   {"content":[{ "style":"normal", "text":"By Will Crowther (1973)
246 |      and Don Woods (1977)"}]},
247 |   {"content":[{ "style":"normal", "text":"Reconstructed in three steps by:"}]},
248 |   {"content":[{ "style":"normal", "text":"Donald Ekman, David M. Baggett (1993)
249 |      and Graham Nelson (1994)"}]},
250 |   {"content":[{ "style":"normal", "text":"[In memoriam Stephen Bishop
251 |      (1820?-1857): GN]"}]},
252 |   {"content":[{ "style":"normal", "text":"Release 5 / Serial number
253 |      961209 / Inform v6.21(G0.33) Library 6/10 "}]},
254 |   {},
255 |   {"content":[{ "style":"subheader", "text":"At End Of Road"}]},
256 |   {"content":[{ "style":"normal", "text":"You are standing at the end of
257 |      a road before a small brick building. Around you is a forest. A small
258 |      stream flows out of the building and down a gully."}]},
259 |   {},
260 |   {"content":[{ "style":"normal", "text":">"}]}
261 |  ] } ],
262 |  "input":[
263 |  {"id":22, "gen":1, "type":"line", "maxlen":256 }
264 |  ]}
265 | 
266 |

267 | 268 | This is just to give you a feel for what's going on. Roughly, this says: 269 |

270 | 271 |

    272 |
  • First update. 273 |
  • Two windows have been opened: 274 |
      275 |
    • A grid (status) window, ID number 25, 80 characters wide, 1 line high; 276 |
    • A buffer (story) window, ID number 22. 277 |
    278 |
  • Both windows were updated this cycle: 279 |
      280 |
    • Window 25 (the status window), line zero, contains the room name and score. 281 |
    • Window 22 (the story window) contains the initial game text. 282 |
        283 |
      • (Each line contains some content, in some style. It so happens that we don't see any mid-line style changes, although they're perfectly possible. {} is a blank line.) 284 |
      285 |
    286 |
  • One window is requesting input this cycle: 287 |
      288 |
    • Window 22 (the story window) requests line input, up to 256 characters' worth. 289 |
    290 |
291 | 292 | (The window ID numbers will vary from run to run, by the way. Window rock numbers are consistent for a given game, but may vary from game to game.) 293 |

294 | 295 |

The first input

296 | 297 | A human types "go east" into some user interface, which then passes the following input to RemGlk: 298 |

299 | 300 |

301 | { "type":"line", "gen":1, "window":22, "value":"go east" }
302 | 
303 |

304 | 305 | The library accepts this, mulls it, and generates a new output update. So it goes. 306 |

307 | 308 |

Referring to image resources

309 | 310 | RemGlk supports Blorb-packaged games which include images. (Sound support is on the to-do list.) However, it does not try to encode the image data in the JSON output. Instead, it assumes that the display library (GlkOte) has access to the same images. When the game draws an image, RemGlk sends a JSON stanza which includes the image number and size. (See the GlkOte documentation.) The display library is then responsible for finding the image with that number. 311 |

312 | 313 | To make this easier, you can configure RemGlk to include an image URL in the stanza. 314 | Do this with the -resourceurl (or -ru) flag: 315 |

316 | 317 |

318 | glulxe -ru http://example.com/datadir/ game.gblorb
319 | 
320 |

321 | 322 | The URL should end with a slash, and should refer to a directory containing image files. RemGlk will send URLs with the format http://example.com/datadir/pict-1.png, http://example.com/datadir/pict-2.jpeg. (Note lower-case, and "jpeg" has an "e".) (This is the same filename convention used by blorbtool.py with the giload command.) 323 |

324 | 325 | You may also specify a local pathname with the -resourcedir (or -rd) flag: 326 | 327 |

328 | glulxe -rd path/dir game.gblorb
329 | 
330 |

331 | 332 | RemGlk will convert the pathname to a file: URL and send it as above. 333 |

334 | 335 |

Referring to data resources

336 | 337 | RemGlk also supports data resources (read using the glk_stream_open_resource() function). 338 |

339 | 340 | Data resources in a Blorb-packaged game are no problem; the game can always access these. However, if the game isn't Blorbed, you must supply the filename for each (numbered) resource. 341 |

342 | 343 |

344 | glulxe -dataresource 3:path/file game.gblorb
345 | 
346 |

347 | 348 | Use -dataresource or -dataresourcebin for a binary file; -dataresourcetext for a text file. (In a Blorb, the chunk type says whether it's binary or text. Outside of a Blorb, it's too dark to read, so you have to specify. Note that the default is binary.) 349 |

350 | 351 |

The Data Format

352 | 353 | I have not written out this documentation in detail. Please refer to the GlkOte documentation. Input what GlkOte outputs, and vice versa. 354 |

355 | 356 | Prior to version 0.2.1, this library used contents and inputs as the names of the top-level data fields, rather than the correct content and input. The prior version also omitted the xpos and ypos fields for grid window input. 357 |

358 | 359 | As of version 0.2.2, the library accepts noninteger numbers. If the field expects an integer, the values will be rounded (towards zero). As of 0.2.4, some of the fields in the metrics object expect non-integer numbers. 360 |

361 | 362 | As of version 0.2.6, the library accepts debuginput events. They are passed along to the game (interpreter) via the gi_debug.h interface. 363 |

364 | 365 | As of version 0.3.0, the library accepts noninteger values in the metrics object. (GlkOte can send fractional values when the browser zoom is changed.) Window positioning will always be in integers, rounded so that everything fits properly. 366 |

367 | 368 | As of version 0.3.1, the library accepts exponential notation for numerical values. If an integer is expected (i.e., everywhere except the metrics object), noninteger values are rounded to the nearest integer. Note that the library does not send exponential notation in its JSON output. 369 |

370 | 371 | As of version 0.3.2, the library adds "exit":true to the update when the game exits. 372 |

373 | 374 | A note on JSON: this is a strict (in both senses) subset of Javascript's data literal format. The biggest differences are: you always use double quotes, never single quotes; strings are always quoted, but numbers never are; an object (dictionary) always has quotes around its keys. 375 |

376 | 377 | All input and output is UTF-8. 378 |

379 | 380 | When generating output, a complete update JSON update will be followed by a blank line (two newlines in a row). You can use this as a hint for breaking the output stream into stanzas. (But RemGlk does not require you to follow this convention when sending it input JSON objects.) 381 |

382 | 383 |

Autosave/autorestore

384 | 385 | Autosave and autorestore are available as of version 0.3.0. 386 | 387 | If the interpreter implements autosave, it will use RemGlk's facilities to store a library serialization dump every turn (before each glk_select() call). This state dump is JSON, but it does not follow the GlkOte format specification. It contains more detailed information, including the "live" state of every window and stream. 388 |

389 | 390 | The autosave format is deliberately not documented; it is specific to the implementation of RemGlk. It is not meant to be transferred between platforms or between interpreters. Use the interpreter's normal .glksave save files for that. 391 |

392 | 393 |


394 | Last updated June 2, 2025. 395 |

396 | 397 | Glk home page 398 |

399 | 400 | 401 | 402 | -------------------------------------------------------------------------------- /cgunicod.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "glk.h" 5 | #include "remglk.h" 6 | 7 | /* This file (and cgunigen.c) are copied directly from the cheapglk package. */ 8 | 9 | void gli_putchar_utf8(glui32 val, FILE *fl) 10 | { 11 | if (val < 0x80) { 12 | putc(val, fl); 13 | } 14 | else if (val < 0x800) { 15 | putc((0xC0 | ((val & 0x7C0) >> 6)), fl); 16 | putc((0x80 | (val & 0x03F) ), fl); 17 | } 18 | else if (val < 0x10000) { 19 | putc((0xE0 | ((val & 0xF000) >> 12)), fl); 20 | putc((0x80 | ((val & 0x0FC0) >> 6)), fl); 21 | putc((0x80 | (val & 0x003F) ), fl); 22 | } 23 | else if (val < 0x200000) { 24 | putc((0xF0 | ((val & 0x1C0000) >> 18)), fl); 25 | putc((0x80 | ((val & 0x03F000) >> 12)), fl); 26 | putc((0x80 | ((val & 0x000FC0) >> 6)), fl); 27 | putc((0x80 | (val & 0x00003F) ), fl); 28 | } 29 | else { 30 | putc('?', fl); 31 | } 32 | } 33 | 34 | int gli_encode_utf8(glui32 val, char *buf, int len) 35 | { 36 | /* return the number of bytes (actually) generated */ 37 | char *ptr = buf; 38 | char *end = buf+len; 39 | 40 | if (val < 0x80) { 41 | if (ptr < end) 42 | *ptr++ = val; 43 | } 44 | else if (val < 0x800) { 45 | if (ptr < end) 46 | *ptr++ = (0xC0 | ((val & 0x7C0) >> 6)); 47 | if (ptr < end) 48 | *ptr++ = (0x80 | (val & 0x03F) ); 49 | } 50 | else if (val < 0x10000) { 51 | if (ptr < end) 52 | *ptr++ = (0xE0 | ((val & 0xF000) >> 12)); 53 | if (ptr < end) 54 | *ptr++ = (0x80 | ((val & 0x0FC0) >> 6)); 55 | if (ptr < end) 56 | *ptr++ = (0x80 | (val & 0x003F) ); 57 | } 58 | else if (val < 0x200000) { 59 | if (ptr < end) 60 | *ptr++ = (0xF0 | ((val & 0x1C0000) >> 18)); 61 | if (ptr < end) 62 | *ptr++ = (0x80 | ((val & 0x03F000) >> 12)); 63 | if (ptr < end) 64 | *ptr++ = (0x80 | ((val & 0x000FC0) >> 6)); 65 | if (ptr < end) 66 | *ptr++ = (0x80 | (val & 0x00003F) ); 67 | } 68 | else { 69 | if (ptr < end) 70 | *ptr++ = '?'; 71 | } 72 | 73 | return (ptr - buf); 74 | } 75 | 76 | glui32 gli_parse_utf8(unsigned char *buf, glui32 buflen, 77 | glui32 *out, glui32 outlen) 78 | { 79 | glui32 pos = 0; 80 | glui32 outpos = 0; 81 | glui32 res; 82 | glui32 val0, val1, val2, val3; 83 | 84 | while (outpos < outlen) { 85 | if (pos >= buflen) 86 | break; 87 | 88 | val0 = buf[pos++]; 89 | 90 | if (val0 < 0x80) { 91 | res = val0; 92 | out[outpos++] = res; 93 | continue; 94 | } 95 | 96 | if ((val0 & 0xe0) == 0xc0) { 97 | if (pos+1 > buflen) { 98 | gli_strict_warning("incomplete two-byte character"); 99 | break; 100 | } 101 | val1 = buf[pos++]; 102 | if ((val1 & 0xc0) != 0x80) { 103 | gli_strict_warning("malformed two-byte character"); 104 | break; 105 | } 106 | res = (val0 & 0x1f) << 6; 107 | res |= (val1 & 0x3f); 108 | out[outpos++] = res; 109 | continue; 110 | } 111 | 112 | if ((val0 & 0xf0) == 0xe0) { 113 | if (pos+2 > buflen) { 114 | gli_strict_warning("incomplete three-byte character"); 115 | break; 116 | } 117 | val1 = buf[pos++]; 118 | val2 = buf[pos++]; 119 | if ((val1 & 0xc0) != 0x80) { 120 | gli_strict_warning("malformed three-byte character"); 121 | break; 122 | } 123 | if ((val2 & 0xc0) != 0x80) { 124 | gli_strict_warning("malformed three-byte character"); 125 | break; 126 | } 127 | res = (((val0 & 0xf)<<12) & 0x0000f000); 128 | res |= (((val1 & 0x3f)<<6) & 0x00000fc0); 129 | res |= (((val2 & 0x3f)) & 0x0000003f); 130 | out[outpos++] = res; 131 | continue; 132 | } 133 | 134 | if ((val0 & 0xf0) == 0xf0) { 135 | if ((val0 & 0xf8) != 0xf0) { 136 | gli_strict_warning("malformed four-byte character"); 137 | break; 138 | } 139 | if (pos+3 > buflen) { 140 | gli_strict_warning("incomplete four-byte character"); 141 | break; 142 | } 143 | val1 = buf[pos++]; 144 | val2 = buf[pos++]; 145 | val3 = buf[pos++]; 146 | if ((val1 & 0xc0) != 0x80) { 147 | gli_strict_warning("malformed four-byte character"); 148 | break; 149 | } 150 | if ((val2 & 0xc0) != 0x80) { 151 | gli_strict_warning("malformed four-byte character"); 152 | break; 153 | } 154 | if ((val3 & 0xc0) != 0x80) { 155 | gli_strict_warning("malformed four-byte character"); 156 | break; 157 | } 158 | res = (((val0 & 0x7)<<18) & 0x1c0000); 159 | res |= (((val1 & 0x3f)<<12) & 0x03f000); 160 | res |= (((val2 & 0x3f)<<6) & 0x000fc0); 161 | res |= (((val3 & 0x3f)) & 0x00003f); 162 | out[outpos++] = res; 163 | continue; 164 | } 165 | 166 | gli_strict_warning("malformed character"); 167 | } 168 | 169 | return outpos; 170 | } 171 | 172 | #ifdef GLK_MODULE_UNICODE 173 | 174 | /* The cgunigen.c file is generated from Unicode data tables, and it's 175 | sort of enormous. Feel free to implement all these case-changing and 176 | normalization functions using your OS's native facilities. */ 177 | 178 | #include "cgunigen.c" 179 | 180 | #define CASE_UPPER (0) 181 | #define CASE_LOWER (1) 182 | #define CASE_TITLE (2) 183 | #define CASE_IDENT (3) 184 | 185 | #define COND_ALL (0) 186 | #define COND_LINESTART (1) 187 | 188 | /* Apply a case change to the buffer. The len is the length of the buffer 189 | array; numchars is the number of characters originally in it. (This 190 | may be less than len.) The result will be clipped to fit len, but 191 | the return value will be the full number of characters that the 192 | converted string should have contained. 193 | */ 194 | static glui32 gli_buffer_change_case(glui32 *buf, glui32 len, 195 | glui32 numchars, int destcase, int cond, int changerest) 196 | { 197 | glui32 ix, jx; 198 | glui32 *outbuf; 199 | glui32 *newoutbuf; 200 | glui32 outcount; 201 | int dest_block_rest, dest_block_first; 202 | int dest_spec_rest, dest_spec_first; 203 | 204 | switch (cond) { 205 | case COND_ALL: 206 | dest_spec_rest = destcase; 207 | dest_spec_first = destcase; 208 | break; 209 | case COND_LINESTART: 210 | if (changerest) 211 | dest_spec_rest = CASE_LOWER; 212 | else 213 | dest_spec_rest = CASE_IDENT; 214 | dest_spec_first = destcase; 215 | break; 216 | default: 217 | return 0; 218 | } 219 | 220 | dest_block_rest = dest_spec_rest; 221 | if (dest_block_rest == CASE_TITLE) 222 | dest_block_rest = CASE_UPPER; 223 | dest_block_first = dest_spec_first; 224 | if (dest_block_first == CASE_TITLE) 225 | dest_block_first = CASE_UPPER; 226 | 227 | newoutbuf = NULL; 228 | outcount = 0; 229 | outbuf = buf; 230 | 231 | for (ix=0; ix len) 304 | finallen = len; 305 | if (finallen) 306 | memcpy(buf, newoutbuf, finallen * sizeof(glui32)); 307 | free(newoutbuf); 308 | } 309 | 310 | return outcount; 311 | } 312 | 313 | glui32 glk_buffer_to_lower_case_uni(glui32 *buf, glui32 len, 314 | glui32 numchars) 315 | { 316 | return gli_buffer_change_case(buf, len, numchars, 317 | CASE_LOWER, COND_ALL, TRUE); 318 | } 319 | 320 | glui32 glk_buffer_to_upper_case_uni(glui32 *buf, glui32 len, 321 | glui32 numchars) 322 | { 323 | return gli_buffer_change_case(buf, len, numchars, 324 | CASE_UPPER, COND_ALL, TRUE); 325 | } 326 | 327 | glui32 glk_buffer_to_title_case_uni(glui32 *buf, glui32 len, 328 | glui32 numchars, glui32 lowerrest) 329 | { 330 | return gli_buffer_change_case(buf, len, numchars, 331 | CASE_TITLE, COND_LINESTART, lowerrest); 332 | } 333 | 334 | #endif /* GLK_MODULE_UNICODE */ 335 | 336 | #ifdef GLK_MODULE_UNICODE_NORM 337 | 338 | /* We're relying on the fact that cgunigen.c has already been included. 339 | So don't try to use GLK_MODULE_UNICODE_NORM without GLK_MODULE_UNICODE. 340 | */ 341 | 342 | static glui32 combining_class(glui32 ch) 343 | { 344 | RETURN_COMBINING_CLASS(ch); 345 | } 346 | 347 | /* This returns a new buffer (possibly longer), containing the decomposed 348 | form of the original buffer. The caller must free the returned buffer. 349 | On exit, *numcharsref contains the size of the returned buffer. 350 | The original buffer is unchanged. 351 | */ 352 | static glui32 *gli_buffer_canon_decompose_uni(glui32 *buf, 353 | glui32 *numcharsref) 354 | { 355 | /* The algorithm for the canonical decomposition of a string: For 356 | each character, look up the decomposition in the decomp table. 357 | Append the decomposition to the buffer. Finally, sort every 358 | substring of the buffer which is made up of combining 359 | characters (characters with a nonzero combining class). */ 360 | 361 | glui32 numchars = *numcharsref; 362 | glui32 destsize = numchars * 2 + 16; 363 | glui32 *dest = (glui32 *)malloc(destsize * sizeof(glui32)); 364 | glui32 destlen = 0; 365 | glui32 ix, jx; 366 | int anycombining = FALSE; 367 | 368 | if (!dest) 369 | return NULL; 370 | 371 | for (ix=0; ix= destsize) { 393 | destsize = destsize * 2; 394 | dest = (glui32 *)realloc(dest, destsize * sizeof(glui32)); 395 | if (!dest) 396 | return NULL; 397 | } 398 | dest[destlen] = ch; 399 | destlen++; 400 | continue; 401 | } 402 | 403 | /* Assume that a character with a decomposition has a 404 | combining class somewhere in there. Not always true, but 405 | it's simpler to assume it. */ 406 | anycombining = TRUE; 407 | 408 | /* We now append count characters to the buffer, reading from 409 | unigen_decomp_data[pos] onwards. None of these characters 410 | are decomposable; that was already recursively expanded when 411 | unigen_decomp_data was generated. */ 412 | 413 | if (destlen+count >= destsize) { 414 | /* Okay, that wasn't enough. Expand more. */ 415 | destsize = destsize * 2 + count; 416 | dest = (glui32 *)realloc(dest, destsize * sizeof(glui32)); 417 | if (!dest) 418 | return NULL; 419 | } 420 | for (jx=0; jx= destlen) 438 | break; 439 | grpstart = ix; 440 | while (ix < destlen && combining_class(dest[ix])) 441 | ix++; 442 | grpend = ix; 443 | if (grpend - grpstart >= 2) { 444 | /* Sort this group. */ 445 | for (jx = grpend-1; jx > grpstart; jx--) { 446 | for (kx = grpstart; kx < jx; kx++) { 447 | if (combining_class(dest[kx]) > combining_class(dest[kx+1])) { 448 | glui32 tmp = dest[kx]; 449 | dest[kx] = dest[kx+1]; 450 | dest[kx+1] = tmp; 451 | } 452 | } 453 | } 454 | } 455 | } 456 | } 457 | 458 | *numcharsref = destlen; 459 | return dest; 460 | } 461 | 462 | static glui32 check_composition(glui32 ch1, glui32 ch2) 463 | { 464 | RETURN_COMPOSITION(ch1, ch2); 465 | } 466 | 467 | /* This composes characters in the given buffer, in place. It returns the 468 | number of characters in the result, which will be less than or equal 469 | to len. 470 | */ 471 | static glui32 gli_buffer_canon_compose_uni(glui32 *buf, glui32 len) 472 | { 473 | /* This algorithm is lifted from the Java sample code at 474 | . 475 | I apologize for the ugly. 476 | 477 | Roughly, pos is the position of the last base character; 478 | curch is that character in progress; ix is the next character 479 | to write (which may fly ahead of pos, as we encounter a string of 480 | combining chars); and jx is the position that we're scanning. 481 | In the simplest case, jx and ix stay together, with pos one behind. */ 482 | 483 | glui32 curch, newch, curclass, newclass, res; 484 | glui32 ix, jx, pos; 485 | 486 | if (len == 0) 487 | return 0; 488 | 489 | pos = 0; 490 | curch = buf[0]; 491 | curclass = combining_class(curch); 492 | if (curclass) 493 | curclass = 999; /* just in case the first character is a combiner */ 494 | ix = 1; 495 | jx = ix; 496 | while (1) { 497 | if (jx >= len) { 498 | buf[pos] = curch; 499 | pos = ix; 500 | break; 501 | } 502 | newch = buf[jx]; 503 | newclass = combining_class(newch); 504 | res = check_composition(curch, newch); 505 | if (res && (!curclass || curclass < newclass)) { 506 | curch = res; 507 | buf[pos] = curch; 508 | } 509 | else { 510 | if (!newclass) { 511 | pos = ix; 512 | curch = newch; 513 | } 514 | curclass = newclass; 515 | buf[ix] = newch; 516 | ix++; 517 | } 518 | jx++; 519 | } 520 | 521 | return pos; 522 | } 523 | 524 | glui32 glk_buffer_canon_decompose_uni(glui32 *buf, glui32 len, 525 | glui32 numchars) 526 | { 527 | glui32 *dest = gli_buffer_canon_decompose_uni(buf, &numchars); 528 | glui32 newlen; 529 | 530 | if (!dest) 531 | return 0; 532 | 533 | /* Copy the data back. */ 534 | newlen = numchars; 535 | if (newlen > len) 536 | newlen = len; 537 | if (newlen) 538 | memcpy(buf, dest, newlen * sizeof(glui32)); 539 | free(dest); 540 | 541 | return numchars; 542 | } 543 | 544 | glui32 glk_buffer_canon_normalize_uni(glui32 *buf, glui32 len, 545 | glui32 numchars) 546 | { 547 | glui32 newlen; 548 | glui32 *dest = gli_buffer_canon_decompose_uni(buf, &numchars); 549 | 550 | if (!dest) 551 | return 0; 552 | 553 | numchars = gli_buffer_canon_compose_uni(dest, numchars); 554 | 555 | /* Copy the data back. */ 556 | newlen = numchars; 557 | if (newlen > len) 558 | newlen = len; 559 | if (newlen) 560 | memcpy(buf, dest, newlen * sizeof(glui32)); 561 | free(dest); 562 | 563 | return numchars; 564 | } 565 | 566 | #endif /* GLK_MODULE_UNICODE_NORM */ 567 | 568 | -------------------------------------------------------------------------------- /glk.h: -------------------------------------------------------------------------------- 1 | #ifndef GLK_H 2 | #define GLK_H 3 | 4 | /* glk.h: Header file for Glk API, version 0.7.6. 5 | Designed by Andrew Plotkin 6 | http://eblong.com/zarf/glk/ 7 | 8 | This file is copyright 1998-2025 by Andrew Plotkin. It is 9 | distributed under the MIT license; see the "LICENSE" file. 10 | */ 11 | 12 | /* If your system does not have , you'll have to remove this 13 | include line. Then edit the definition of glui32 to make sure it's 14 | really a 32-bit unsigned integer type, and glsi32 to make sure 15 | it's really a 32-bit signed integer type. If they're not, horrible 16 | things will happen. */ 17 | #include 18 | typedef uint32_t glui32; 19 | typedef int32_t glsi32; 20 | 21 | /* These are the compile-time conditionals that reveal various Glk optional 22 | modules. Note that if GLK_MODULE_SOUND2 is defined, GLK_MODULE_SOUND 23 | must be also. */ 24 | #define GLK_MODULE_LINE_ECHO 25 | #define GLK_MODULE_LINE_TERMINATORS 26 | #define GLK_MODULE_UNICODE 27 | #define GLK_MODULE_UNICODE_NORM 28 | #define GLK_MODULE_IMAGE 29 | #define GLK_MODULE_IMAGE2 30 | #define GLK_MODULE_SOUND 31 | #define GLK_MODULE_SOUND2 32 | #define GLK_MODULE_HYPERLINKS 33 | #define GLK_MODULE_DATETIME 34 | #define GLK_MODULE_RESOURCE_STREAM 35 | 36 | /* Define a macro for a function attribute that indicates a function that 37 | never returns. (E.g., glk_exit().) We try to do this only in C compilers 38 | that support it. If this is causing you problems, comment all this out 39 | and simply "#define GLK_ATTRIBUTE_NORETURN". */ 40 | #if defined(__GNUC__) || defined(__clang__) 41 | #define GLK_ATTRIBUTE_NORETURN __attribute__((__noreturn__)) 42 | #endif /* defined(__GNUC__) || defined(__clang__) */ 43 | #ifndef GLK_ATTRIBUTE_NORETURN 44 | #define GLK_ATTRIBUTE_NORETURN 45 | #endif /* GLK_ATTRIBUTE_NORETURN */ 46 | 47 | /* These types are opaque object identifiers. They're pointers to opaque 48 | C structures, which are defined differently by each library. */ 49 | typedef struct glk_window_struct *winid_t; 50 | typedef struct glk_stream_struct *strid_t; 51 | typedef struct glk_fileref_struct *frefid_t; 52 | typedef struct glk_schannel_struct *schanid_t; 53 | 54 | #define gestalt_Version (0) 55 | #define gestalt_CharInput (1) 56 | #define gestalt_LineInput (2) 57 | #define gestalt_CharOutput (3) 58 | #define gestalt_CharOutput_CannotPrint (0) 59 | #define gestalt_CharOutput_ApproxPrint (1) 60 | #define gestalt_CharOutput_ExactPrint (2) 61 | #define gestalt_MouseInput (4) 62 | #define gestalt_Timer (5) 63 | #define gestalt_Graphics (6) 64 | #define gestalt_DrawImage (7) 65 | #define gestalt_Sound (8) 66 | #define gestalt_SoundVolume (9) 67 | #define gestalt_SoundNotify (10) 68 | #define gestalt_Hyperlinks (11) 69 | #define gestalt_HyperlinkInput (12) 70 | #define gestalt_SoundMusic (13) 71 | #define gestalt_GraphicsTransparency (14) 72 | #define gestalt_Unicode (15) 73 | #define gestalt_UnicodeNorm (16) 74 | #define gestalt_LineInputEcho (17) 75 | #define gestalt_LineTerminators (18) 76 | #define gestalt_LineTerminatorKey (19) 77 | #define gestalt_DateTime (20) 78 | #define gestalt_Sound2 (21) 79 | #define gestalt_ResourceStream (22) 80 | #define gestalt_GraphicsCharInput (23) 81 | #define gestalt_DrawImageScale (24) 82 | 83 | #define evtype_None (0) 84 | #define evtype_Timer (1) 85 | #define evtype_CharInput (2) 86 | #define evtype_LineInput (3) 87 | #define evtype_MouseInput (4) 88 | #define evtype_Arrange (5) 89 | #define evtype_Redraw (6) 90 | #define evtype_SoundNotify (7) 91 | #define evtype_Hyperlink (8) 92 | #define evtype_VolumeNotify (9) 93 | 94 | typedef struct event_struct { 95 | glui32 type; 96 | winid_t win; 97 | glui32 val1, val2; 98 | } event_t; 99 | 100 | #define keycode_Unknown (0xffffffff) 101 | #define keycode_Left (0xfffffffe) 102 | #define keycode_Right (0xfffffffd) 103 | #define keycode_Up (0xfffffffc) 104 | #define keycode_Down (0xfffffffb) 105 | #define keycode_Return (0xfffffffa) 106 | #define keycode_Delete (0xfffffff9) 107 | #define keycode_Escape (0xfffffff8) 108 | #define keycode_Tab (0xfffffff7) 109 | #define keycode_PageUp (0xfffffff6) 110 | #define keycode_PageDown (0xfffffff5) 111 | #define keycode_Home (0xfffffff4) 112 | #define keycode_End (0xfffffff3) 113 | #define keycode_Func1 (0xffffffef) 114 | #define keycode_Func2 (0xffffffee) 115 | #define keycode_Func3 (0xffffffed) 116 | #define keycode_Func4 (0xffffffec) 117 | #define keycode_Func5 (0xffffffeb) 118 | #define keycode_Func6 (0xffffffea) 119 | #define keycode_Func7 (0xffffffe9) 120 | #define keycode_Func8 (0xffffffe8) 121 | #define keycode_Func9 (0xffffffe7) 122 | #define keycode_Func10 (0xffffffe6) 123 | #define keycode_Func11 (0xffffffe5) 124 | #define keycode_Func12 (0xffffffe4) 125 | /* The last keycode is always (0x100000000 - keycode_MAXVAL) */ 126 | #define keycode_MAXVAL (28) 127 | 128 | #define style_Normal (0) 129 | #define style_Emphasized (1) 130 | #define style_Preformatted (2) 131 | #define style_Header (3) 132 | #define style_Subheader (4) 133 | #define style_Alert (5) 134 | #define style_Note (6) 135 | #define style_BlockQuote (7) 136 | #define style_Input (8) 137 | #define style_User1 (9) 138 | #define style_User2 (10) 139 | #define style_NUMSTYLES (11) 140 | 141 | typedef struct stream_result_struct { 142 | glui32 readcount; 143 | glui32 writecount; 144 | } stream_result_t; 145 | 146 | #define wintype_AllTypes (0) 147 | #define wintype_Pair (1) 148 | #define wintype_Blank (2) 149 | #define wintype_TextBuffer (3) 150 | #define wintype_TextGrid (4) 151 | #define wintype_Graphics (5) 152 | 153 | #define winmethod_Left (0x00) 154 | #define winmethod_Right (0x01) 155 | #define winmethod_Above (0x02) 156 | #define winmethod_Below (0x03) 157 | #define winmethod_DirMask (0x0f) 158 | 159 | #define winmethod_Fixed (0x10) 160 | #define winmethod_Proportional (0x20) 161 | #define winmethod_DivisionMask (0xf0) 162 | 163 | #define winmethod_Border (0x000) 164 | #define winmethod_NoBorder (0x100) 165 | #define winmethod_BorderMask (0x100) 166 | 167 | #define fileusage_Data (0x00) 168 | #define fileusage_SavedGame (0x01) 169 | #define fileusage_Transcript (0x02) 170 | #define fileusage_InputRecord (0x03) 171 | #define fileusage_TypeMask (0x0f) 172 | 173 | #define fileusage_TextMode (0x100) 174 | #define fileusage_BinaryMode (0x000) 175 | 176 | #define filemode_Write (0x01) 177 | #define filemode_Read (0x02) 178 | #define filemode_ReadWrite (0x03) 179 | #define filemode_WriteAppend (0x05) 180 | 181 | #define seekmode_Start (0) 182 | #define seekmode_Current (1) 183 | #define seekmode_End (2) 184 | 185 | #define stylehint_Indentation (0) 186 | #define stylehint_ParaIndentation (1) 187 | #define stylehint_Justification (2) 188 | #define stylehint_Size (3) 189 | #define stylehint_Weight (4) 190 | #define stylehint_Oblique (5) 191 | #define stylehint_Proportional (6) 192 | #define stylehint_TextColor (7) 193 | #define stylehint_BackColor (8) 194 | #define stylehint_ReverseColor (9) 195 | #define stylehint_NUMHINTS (10) 196 | 197 | #define stylehint_just_LeftFlush (0) 198 | #define stylehint_just_LeftRight (1) 199 | #define stylehint_just_Centered (2) 200 | #define stylehint_just_RightFlush (3) 201 | 202 | /* glk_main() is the top-level function which you define. The Glk library 203 | calls it. */ 204 | extern void glk_main(void); 205 | 206 | extern void glk_exit(void) GLK_ATTRIBUTE_NORETURN; 207 | extern void glk_set_interrupt_handler(void (*func)(void)); 208 | extern void glk_tick(void); 209 | 210 | extern glui32 glk_gestalt(glui32 sel, glui32 val); 211 | extern glui32 glk_gestalt_ext(glui32 sel, glui32 val, glui32 *arr, 212 | glui32 arrlen); 213 | 214 | extern unsigned char glk_char_to_lower(unsigned char ch); 215 | extern unsigned char glk_char_to_upper(unsigned char ch); 216 | 217 | extern winid_t glk_window_get_root(void); 218 | extern winid_t glk_window_open(winid_t split, glui32 method, glui32 size, 219 | glui32 wintype, glui32 rock); 220 | extern void glk_window_close(winid_t win, stream_result_t *result); 221 | extern void glk_window_get_size(winid_t win, glui32 *widthptr, 222 | glui32 *heightptr); 223 | extern void glk_window_set_arrangement(winid_t win, glui32 method, 224 | glui32 size, winid_t keywin); 225 | extern void glk_window_get_arrangement(winid_t win, glui32 *methodptr, 226 | glui32 *sizeptr, winid_t *keywinptr); 227 | extern winid_t glk_window_iterate(winid_t win, glui32 *rockptr); 228 | extern glui32 glk_window_get_rock(winid_t win); 229 | extern glui32 glk_window_get_type(winid_t win); 230 | extern winid_t glk_window_get_parent(winid_t win); 231 | extern winid_t glk_window_get_sibling(winid_t win); 232 | extern void glk_window_clear(winid_t win); 233 | extern void glk_window_move_cursor(winid_t win, glui32 xpos, glui32 ypos); 234 | 235 | extern strid_t glk_window_get_stream(winid_t win); 236 | extern void glk_window_set_echo_stream(winid_t win, strid_t str); 237 | extern strid_t glk_window_get_echo_stream(winid_t win); 238 | extern void glk_set_window(winid_t win); 239 | 240 | extern strid_t glk_stream_open_file(frefid_t fileref, glui32 fmode, 241 | glui32 rock); 242 | extern strid_t glk_stream_open_memory(char *buf, glui32 buflen, glui32 fmode, 243 | glui32 rock); 244 | extern void glk_stream_close(strid_t str, stream_result_t *result); 245 | extern strid_t glk_stream_iterate(strid_t str, glui32 *rockptr); 246 | extern glui32 glk_stream_get_rock(strid_t str); 247 | extern void glk_stream_set_position(strid_t str, glsi32 pos, glui32 seekmode); 248 | extern glui32 glk_stream_get_position(strid_t str); 249 | extern void glk_stream_set_current(strid_t str); 250 | extern strid_t glk_stream_get_current(void); 251 | 252 | extern void glk_put_char(unsigned char ch); 253 | extern void glk_put_char_stream(strid_t str, unsigned char ch); 254 | extern void glk_put_string(char *s); 255 | extern void glk_put_string_stream(strid_t str, char *s); 256 | extern void glk_put_buffer(char *buf, glui32 len); 257 | extern void glk_put_buffer_stream(strid_t str, char *buf, glui32 len); 258 | extern void glk_set_style(glui32 styl); 259 | extern void glk_set_style_stream(strid_t str, glui32 styl); 260 | 261 | extern glsi32 glk_get_char_stream(strid_t str); 262 | extern glui32 glk_get_line_stream(strid_t str, char *buf, glui32 len); 263 | extern glui32 glk_get_buffer_stream(strid_t str, char *buf, glui32 len); 264 | 265 | extern void glk_stylehint_set(glui32 wintype, glui32 styl, glui32 hint, 266 | glsi32 val); 267 | extern void glk_stylehint_clear(glui32 wintype, glui32 styl, glui32 hint); 268 | extern glui32 glk_style_distinguish(winid_t win, glui32 styl1, glui32 styl2); 269 | extern glui32 glk_style_measure(winid_t win, glui32 styl, glui32 hint, 270 | glui32 *result); 271 | 272 | extern frefid_t glk_fileref_create_temp(glui32 usage, glui32 rock); 273 | extern frefid_t glk_fileref_create_by_name(glui32 usage, char *name, 274 | glui32 rock); 275 | extern frefid_t glk_fileref_create_by_prompt(glui32 usage, glui32 fmode, 276 | glui32 rock); 277 | extern frefid_t glk_fileref_create_from_fileref(glui32 usage, frefid_t fref, 278 | glui32 rock); 279 | extern void glk_fileref_destroy(frefid_t fref); 280 | extern frefid_t glk_fileref_iterate(frefid_t fref, glui32 *rockptr); 281 | extern glui32 glk_fileref_get_rock(frefid_t fref); 282 | extern void glk_fileref_delete_file(frefid_t fref); 283 | extern glui32 glk_fileref_does_file_exist(frefid_t fref); 284 | 285 | extern void glk_select(event_t *event); 286 | extern void glk_select_poll(event_t *event); 287 | 288 | extern void glk_request_timer_events(glui32 millisecs); 289 | 290 | extern void glk_request_line_event(winid_t win, char *buf, glui32 maxlen, 291 | glui32 initlen); 292 | extern void glk_request_char_event(winid_t win); 293 | extern void glk_request_mouse_event(winid_t win); 294 | 295 | extern void glk_cancel_line_event(winid_t win, event_t *event); 296 | extern void glk_cancel_char_event(winid_t win); 297 | extern void glk_cancel_mouse_event(winid_t win); 298 | 299 | #ifdef GLK_MODULE_LINE_ECHO 300 | extern void glk_set_echo_line_event(winid_t win, glui32 val); 301 | #endif /* GLK_MODULE_LINE_ECHO */ 302 | 303 | #ifdef GLK_MODULE_LINE_TERMINATORS 304 | extern void glk_set_terminators_line_event(winid_t win, glui32 *keycodes, 305 | glui32 count); 306 | #endif /* GLK_MODULE_LINE_TERMINATORS */ 307 | 308 | #ifdef GLK_MODULE_UNICODE 309 | 310 | extern glui32 glk_buffer_to_lower_case_uni(glui32 *buf, glui32 len, 311 | glui32 numchars); 312 | extern glui32 glk_buffer_to_upper_case_uni(glui32 *buf, glui32 len, 313 | glui32 numchars); 314 | extern glui32 glk_buffer_to_title_case_uni(glui32 *buf, glui32 len, 315 | glui32 numchars, glui32 lowerrest); 316 | 317 | extern void glk_put_char_uni(glui32 ch); 318 | extern void glk_put_string_uni(glui32 *s); 319 | extern void glk_put_buffer_uni(glui32 *buf, glui32 len); 320 | extern void glk_put_char_stream_uni(strid_t str, glui32 ch); 321 | extern void glk_put_string_stream_uni(strid_t str, glui32 *s); 322 | extern void glk_put_buffer_stream_uni(strid_t str, glui32 *buf, glui32 len); 323 | 324 | extern glsi32 glk_get_char_stream_uni(strid_t str); 325 | extern glui32 glk_get_buffer_stream_uni(strid_t str, glui32 *buf, glui32 len); 326 | extern glui32 glk_get_line_stream_uni(strid_t str, glui32 *buf, glui32 len); 327 | 328 | extern strid_t glk_stream_open_file_uni(frefid_t fileref, glui32 fmode, 329 | glui32 rock); 330 | extern strid_t glk_stream_open_memory_uni(glui32 *buf, glui32 buflen, 331 | glui32 fmode, glui32 rock); 332 | 333 | extern void glk_request_char_event_uni(winid_t win); 334 | extern void glk_request_line_event_uni(winid_t win, glui32 *buf, 335 | glui32 maxlen, glui32 initlen); 336 | 337 | #endif /* GLK_MODULE_UNICODE */ 338 | 339 | #ifdef GLK_MODULE_UNICODE_NORM 340 | 341 | extern glui32 glk_buffer_canon_decompose_uni(glui32 *buf, glui32 len, 342 | glui32 numchars); 343 | extern glui32 glk_buffer_canon_normalize_uni(glui32 *buf, glui32 len, 344 | glui32 numchars); 345 | 346 | #endif /* GLK_MODULE_UNICODE_NORM */ 347 | 348 | #ifdef GLK_MODULE_IMAGE 349 | 350 | #define imagealign_InlineUp (0x01) 351 | #define imagealign_InlineDown (0x02) 352 | #define imagealign_InlineCenter (0x03) 353 | #define imagealign_MarginLeft (0x04) 354 | #define imagealign_MarginRight (0x05) 355 | 356 | extern glui32 glk_image_draw(winid_t win, glui32 image, glsi32 val1, glsi32 val2); 357 | extern glui32 glk_image_draw_scaled(winid_t win, glui32 image, 358 | glsi32 val1, glsi32 val2, glui32 width, glui32 height); 359 | extern glui32 glk_image_get_info(glui32 image, glui32 *width, glui32 *height); 360 | 361 | extern void glk_window_flow_break(winid_t win); 362 | 363 | extern void glk_window_erase_rect(winid_t win, 364 | glsi32 left, glsi32 top, glui32 width, glui32 height); 365 | extern void glk_window_fill_rect(winid_t win, glui32 color, 366 | glsi32 left, glsi32 top, glui32 width, glui32 height); 367 | extern void glk_window_set_background_color(winid_t win, glui32 color); 368 | 369 | #ifdef GLK_MODULE_IMAGE2 370 | /* Note that this section is nested inside the #ifdef GLK_MODULE_IMAGE. 371 | GLK_MODULE_IMAGE must be defined if GLK_MODULE_IMAGE2 is. */ 372 | 373 | #define imagerule_WidthOrig (0x01) 374 | #define imagerule_WidthFixed (0x02) 375 | #define imagerule_WidthRatio (0x03) 376 | #define imagerule_WidthMask (0x03) 377 | #define imagerule_HeightOrig (0x04) 378 | #define imagerule_HeightFixed (0x08) 379 | #define imagerule_AspectRatio (0x0C) 380 | #define imagerule_HeightMask (0x0C) 381 | 382 | extern glui32 glk_image_draw_scaled_ext(winid_t win, glui32 image, 383 | glsi32 val1, glsi32 val2, glui32 width, glui32 height, 384 | glui32 imagerule, glui32 maxwidth); 385 | 386 | #endif /* GLK_MODULE_IMAGE2 */ 387 | #endif /* GLK_MODULE_IMAGE */ 388 | 389 | #ifdef GLK_MODULE_SOUND 390 | 391 | extern schanid_t glk_schannel_create(glui32 rock); 392 | extern void glk_schannel_destroy(schanid_t chan); 393 | extern schanid_t glk_schannel_iterate(schanid_t chan, glui32 *rockptr); 394 | extern glui32 glk_schannel_get_rock(schanid_t chan); 395 | 396 | extern glui32 glk_schannel_play(schanid_t chan, glui32 snd); 397 | extern glui32 glk_schannel_play_ext(schanid_t chan, glui32 snd, glui32 repeats, 398 | glui32 notify); 399 | extern void glk_schannel_stop(schanid_t chan); 400 | extern void glk_schannel_set_volume(schanid_t chan, glui32 vol); 401 | 402 | extern void glk_sound_load_hint(glui32 snd, glui32 flag); 403 | 404 | #ifdef GLK_MODULE_SOUND2 405 | /* Note that this section is nested inside the #ifdef GLK_MODULE_SOUND. 406 | GLK_MODULE_SOUND must be defined if GLK_MODULE_SOUND2 is. */ 407 | 408 | extern schanid_t glk_schannel_create_ext(glui32 rock, glui32 volume); 409 | extern glui32 glk_schannel_play_multi(schanid_t *chanarray, glui32 chancount, 410 | glui32 *sndarray, glui32 soundcount, glui32 notify); 411 | extern void glk_schannel_pause(schanid_t chan); 412 | extern void glk_schannel_unpause(schanid_t chan); 413 | extern void glk_schannel_set_volume_ext(schanid_t chan, glui32 vol, 414 | glui32 duration, glui32 notify); 415 | 416 | #endif /* GLK_MODULE_SOUND2 */ 417 | #endif /* GLK_MODULE_SOUND */ 418 | 419 | #ifdef GLK_MODULE_HYPERLINKS 420 | 421 | extern void glk_set_hyperlink(glui32 linkval); 422 | extern void glk_set_hyperlink_stream(strid_t str, glui32 linkval); 423 | extern void glk_request_hyperlink_event(winid_t win); 424 | extern void glk_cancel_hyperlink_event(winid_t win); 425 | 426 | #endif /* GLK_MODULE_HYPERLINKS */ 427 | 428 | #ifdef GLK_MODULE_DATETIME 429 | 430 | typedef struct glktimeval_struct { 431 | glsi32 high_sec; 432 | glui32 low_sec; 433 | glsi32 microsec; 434 | } glktimeval_t; 435 | 436 | typedef struct glkdate_struct { 437 | glsi32 year; /* full (four-digit) year */ 438 | glsi32 month; /* 1-12, 1 is January */ 439 | glsi32 day; /* 1-31 */ 440 | glsi32 weekday; /* 0-6, 0 is Sunday */ 441 | glsi32 hour; /* 0-23 */ 442 | glsi32 minute; /* 0-59 */ 443 | glsi32 second; /* 0-59, maybe 60 during a leap second */ 444 | glsi32 microsec; /* 0-999999 */ 445 | } glkdate_t; 446 | 447 | extern void glk_current_time(glktimeval_t *time); 448 | extern glsi32 glk_current_simple_time(glui32 factor); 449 | extern void glk_time_to_date_utc(glktimeval_t *time, glkdate_t *date); 450 | extern void glk_time_to_date_local(glktimeval_t *time, glkdate_t *date); 451 | extern void glk_simple_time_to_date_utc(glsi32 time, glui32 factor, 452 | glkdate_t *date); 453 | extern void glk_simple_time_to_date_local(glsi32 time, glui32 factor, 454 | glkdate_t *date); 455 | extern void glk_date_to_time_utc(glkdate_t *date, glktimeval_t *time); 456 | extern void glk_date_to_time_local(glkdate_t *date, glktimeval_t *time); 457 | extern glsi32 glk_date_to_simple_time_utc(glkdate_t *date, glui32 factor); 458 | extern glsi32 glk_date_to_simple_time_local(glkdate_t *date, glui32 factor); 459 | 460 | #endif /* GLK_MODULE_DATETIME */ 461 | 462 | #ifdef GLK_MODULE_RESOURCE_STREAM 463 | 464 | extern strid_t glk_stream_open_resource(glui32 filenum, glui32 rock); 465 | extern strid_t glk_stream_open_resource_uni(glui32 filenum, glui32 rock); 466 | 467 | #endif /* GLK_MODULE_RESOURCE_STREAM */ 468 | 469 | #endif /* GLK_H */ 470 | -------------------------------------------------------------------------------- /rgwin_buf.c: -------------------------------------------------------------------------------- 1 | /* rgwin_buf.c: The buffer window type 2 | for RemGlk, remote-procedure-call implementation of the Glk API. 3 | Designed by Andrew Plotkin 4 | http://eblong.com/zarf/glk/ 5 | */ 6 | 7 | #include 8 | #include 9 | #include 10 | #include "glk.h" 11 | #include "remglk.h" 12 | #include "rgdata.h" 13 | #include "rgwin_buf.h" 14 | 15 | /* Maximum buffer size. The slack value is how much larger than the size 16 | we should get before we trim. */ 17 | #define BUFFER_SIZE (5000) 18 | #define BUFFER_SLACK (1000) 19 | 20 | static long find_style_by_pos(window_textbuffer_t *dwin, long pos); 21 | static void set_last_run(window_textbuffer_t *dwin, glui32 style, glui32 hyperlink); 22 | 23 | window_textbuffer_t *win_textbuffer_create(window_t *win) 24 | { 25 | window_textbuffer_t *dwin = (window_textbuffer_t *)malloc(sizeof(window_textbuffer_t)); 26 | dwin->owner = win; 27 | 28 | dwin->numchars = 0; 29 | dwin->charssize = 500; 30 | dwin->chars = (glui32 *)malloc(dwin->charssize * sizeof(glui32)); 31 | 32 | dwin->numspecials = 0; 33 | dwin->specialssize = 4; 34 | dwin->specials = (data_specialspan_t **)malloc(dwin->specialssize * sizeof(data_specialspan_t *)); 35 | 36 | dwin->numruns = 0; 37 | dwin->runssize = 40; 38 | dwin->runs = (tbrun_t *)malloc(dwin->runssize * sizeof(tbrun_t)); 39 | 40 | if (!dwin->chars || !dwin->runs) 41 | return NULL; 42 | 43 | dwin->inbuf = NULL; 44 | dwin->incurpos = 0; 45 | dwin->inunicode = FALSE; 46 | dwin->inecho = FALSE; 47 | dwin->intermkeys = 0; 48 | dwin->inmax = 0; 49 | dwin->origstyle = 0; 50 | dwin->orighyperlink = 0; 51 | 52 | dwin->inarrayrock.num = 0; 53 | 54 | dwin->numruns = 1; 55 | dwin->runs[0].style = style_Normal; 56 | dwin->runs[0].hyperlink = 0; 57 | dwin->runs[0].specialnum = -1; 58 | dwin->runs[0].pos = 0; 59 | 60 | dwin->updatemark = 0; 61 | dwin->startclear = FALSE; 62 | 63 | dwin->width = -1; 64 | dwin->height = -1; 65 | 66 | return dwin; 67 | } 68 | 69 | void win_textbuffer_destroy(window_textbuffer_t *dwin) 70 | { 71 | if (dwin->inbuf) { 72 | if (gli_unregister_arr) { 73 | char *typedesc = (dwin->inunicode ? "&+#!Iu" : "&+#!Cn"); 74 | (*gli_unregister_arr)(dwin->inbuf, dwin->inmax, typedesc, dwin->inarrayrock); 75 | } 76 | dwin->inbuf = NULL; 77 | } 78 | 79 | dwin->owner = NULL; 80 | 81 | if (dwin->runs) { 82 | free(dwin->runs); 83 | dwin->runs = NULL; 84 | } 85 | 86 | if (dwin->specials) { 87 | long px; 88 | for (px=0; pxnumspecials; px++) 89 | data_specialspan_free(dwin->specials[px]); 90 | free(dwin->specials); 91 | dwin->specials = NULL; 92 | } 93 | 94 | if (dwin->chars) { 95 | free(dwin->chars); 96 | dwin->chars = NULL; 97 | } 98 | 99 | free(dwin); 100 | } 101 | 102 | void win_textbuffer_rearrange(window_t *win, grect_t *box, data_metrics_t *metrics) 103 | { 104 | window_textbuffer_t *dwin = win->data; 105 | dwin->owner->bbox = *box; 106 | 107 | dwin->width = box->right - box->left; 108 | dwin->height = box->bottom - box->top; 109 | } 110 | 111 | /* Find the last stylerun for which pos >= style.pos. We know run[0].pos == 0, 112 | so the result is always >= 0. */ 113 | static long find_style_by_pos(window_textbuffer_t *dwin, long pos) 114 | { 115 | long beg, end, val; 116 | tbrun_t *runs = dwin->runs; 117 | 118 | /* Do a binary search, maintaining 119 | runs[beg].pos <= pos < runs[end].pos 120 | (we pretend that runs[numruns].pos is infinity) */ 121 | 122 | beg = 0; 123 | end = dwin->numruns; 124 | 125 | while (beg+1 < end) { 126 | val = (beg+end) / 2; 127 | if (pos >= runs[val].pos) { 128 | beg = val; 129 | } 130 | else { 131 | end = val; 132 | } 133 | } 134 | 135 | return beg; 136 | } 137 | 138 | data_content_t *win_textbuffer_update(window_t *win) 139 | { 140 | window_textbuffer_t *dwin = win->data; 141 | long snum, cnum, spanstart; 142 | long nextrunpos; 143 | short curstyle; 144 | glui32 curlink; 145 | long curspecnum; 146 | 147 | if (dwin->updatemark >= dwin->numchars && !dwin->startclear) { 148 | return NULL; 149 | } 150 | 151 | data_content_t *dat = data_content_alloc(win->updatetag, win->type); 152 | 153 | if (dwin->startclear) 154 | dat->clear = TRUE; 155 | 156 | if (TRUE) { 157 | cnum = dwin->updatemark; 158 | spanstart = cnum; 159 | snum = find_style_by_pos(dwin, cnum); 160 | curstyle = dwin->runs[snum].style; 161 | curlink = dwin->runs[snum].hyperlink; 162 | curspecnum = dwin->runs[snum].specialnum; 163 | if (snum+1 < dwin->numruns) 164 | nextrunpos = dwin->runs[snum+1].pos; 165 | else 166 | nextrunpos = dwin->numchars+1; 167 | 168 | data_line_t *line = data_line_alloc(); 169 | gen_list_append(&dat->lines, line); 170 | line->append = TRUE; 171 | 172 | while (cnum < dwin->numchars) { 173 | glui32 ch = dwin->chars[cnum]; 174 | if (ch == '\n') { 175 | if (cnum > spanstart) { 176 | if (curspecnum >= 0) { 177 | data_specialspan_t *curspecial = dwin->specials[curspecnum]; 178 | data_line_add_specialspan(line, curspecial); 179 | } 180 | else { 181 | data_line_add_span(line, curstyle, curlink, dwin->chars+spanstart, cnum-spanstart); 182 | } 183 | spanstart = cnum; 184 | } 185 | 186 | line = data_line_alloc(); 187 | gen_list_append(&dat->lines, line); 188 | line->append = FALSE; 189 | 190 | cnum++; 191 | spanstart = cnum; 192 | continue; 193 | } 194 | 195 | while (cnum >= nextrunpos) { 196 | if (cnum > spanstart) { 197 | if (curspecnum >= 0) { 198 | data_specialspan_t *curspecial = dwin->specials[curspecnum]; 199 | data_line_add_specialspan(line, curspecial); 200 | } 201 | else { 202 | data_line_add_span(line, curstyle, curlink, dwin->chars+spanstart, cnum-spanstart); 203 | } 204 | spanstart = cnum; 205 | } 206 | 207 | snum++; 208 | curstyle = dwin->runs[snum].style; 209 | curlink = dwin->runs[snum].hyperlink; 210 | curspecnum = dwin->runs[snum].specialnum; 211 | if (snum+1 < dwin->numruns) 212 | nextrunpos = dwin->runs[snum+1].pos; 213 | else 214 | nextrunpos = dwin->numchars+1; 215 | } 216 | 217 | cnum++; 218 | } 219 | 220 | if (cnum > spanstart) { 221 | if (curspecnum >= 0) { 222 | data_specialspan_t *curspecial = dwin->specials[curspecnum]; 223 | data_line_add_specialspan(line, curspecial); 224 | } 225 | else { 226 | data_line_add_span(line, curstyle, curlink, dwin->chars+spanstart, cnum-spanstart); 227 | } 228 | spanstart = cnum; 229 | } 230 | } 231 | 232 | dwin->updatemark = dwin->numchars; 233 | dwin->startclear = FALSE; 234 | 235 | return dat; 236 | } 237 | 238 | void win_textbuffer_putchar(window_t *win, glui32 ch) 239 | { 240 | window_textbuffer_t *dwin = win->data; 241 | long lx; 242 | 243 | if (dwin->numchars >= dwin->charssize) { 244 | dwin->charssize *= 2; 245 | dwin->chars = (glui32 *)realloc(dwin->chars, 246 | dwin->charssize * sizeof(glui32)); 247 | } 248 | 249 | lx = dwin->numchars; 250 | 251 | if (dwin->runs[dwin->numruns-1].specialnum >= 0 252 | || win->style != dwin->runs[dwin->numruns-1].style 253 | || win->hyperlink != dwin->runs[dwin->numruns-1].hyperlink) { 254 | set_last_run(dwin, win->style, win->hyperlink); 255 | } 256 | 257 | dwin->chars[lx] = ch; 258 | dwin->numchars++; 259 | } 260 | 261 | void win_textbuffer_putspecial(window_t *win, data_specialspan_t *special) 262 | { 263 | /* Takes ownership of the specialspan object. It will live until the 264 | window is cleared or trimmed. */ 265 | 266 | window_textbuffer_t *dwin = win->data; 267 | long lx, px; 268 | 269 | if (dwin->numchars >= dwin->charssize) { 270 | dwin->charssize *= 2; 271 | dwin->chars = (glui32 *)realloc(dwin->chars, 272 | dwin->charssize * sizeof(glui32)); 273 | } 274 | 275 | lx = dwin->numchars; 276 | 277 | if (dwin->numspecials >= dwin->specialssize) { 278 | dwin->specialssize *= 2; 279 | dwin->specials = (data_specialspan_t **)realloc(dwin->specials, 280 | dwin->specialssize * sizeof(data_specialspan_t *)); 281 | } 282 | 283 | px = dwin->numspecials; 284 | 285 | dwin->specials[px] = special; 286 | dwin->numspecials++; 287 | 288 | /* A special is always a new run. */ 289 | set_last_run(dwin, win->style, win->hyperlink); 290 | dwin->runs[dwin->numruns-1].specialnum = px; 291 | 292 | dwin->chars[lx] = '#'; /* dummy char (not a newline!) */ 293 | dwin->numchars++; 294 | } 295 | 296 | /* If the last (dangling) run is empty, set its style/link attributes. 297 | Otherwise, add a new empty run with those attributes. */ 298 | static void set_last_run(window_textbuffer_t *dwin, glui32 style, glui32 hyperlink) 299 | { 300 | long lx = dwin->numchars; 301 | long rx = dwin->numruns-1; 302 | 303 | if (dwin->runs[rx].pos == lx) { 304 | dwin->runs[rx].style = style; 305 | dwin->runs[rx].hyperlink = hyperlink; 306 | dwin->runs[rx].specialnum = -1; 307 | } 308 | else { 309 | rx++; 310 | if (rx >= dwin->runssize) { 311 | dwin->runssize *= 2; 312 | dwin->runs = (tbrun_t *)realloc(dwin->runs, 313 | dwin->runssize * sizeof(tbrun_t)); 314 | } 315 | dwin->runs[rx].pos = lx; 316 | dwin->runs[rx].style = style; 317 | dwin->runs[rx].hyperlink = hyperlink; 318 | dwin->runs[rx].specialnum = -1; 319 | dwin->numruns++; 320 | } 321 | 322 | } 323 | 324 | void win_textbuffer_clear(window_t *win) 325 | { 326 | window_textbuffer_t *dwin = win->data; 327 | long px; 328 | 329 | for (px=0; pxnumspecials; px++) { 330 | data_specialspan_free(dwin->specials[px]); 331 | dwin->specials[px] = NULL; 332 | } 333 | dwin->numspecials = 0; 334 | 335 | dwin->numchars = 0; 336 | 337 | dwin->numruns = 1; 338 | dwin->runs[0].style = win->style; 339 | dwin->runs[0].hyperlink = win->hyperlink; 340 | dwin->runs[0].specialnum = -1; 341 | dwin->runs[0].pos = 0; 342 | 343 | dwin->updatemark = 0; 344 | dwin->startclear = TRUE; 345 | } 346 | 347 | void win_textbuffer_trim_buffer(window_t *win) 348 | { 349 | window_textbuffer_t *dwin = win->data; 350 | long snum, cnum, specnum; 351 | long rx, px; 352 | 353 | if (dwin->numchars <= BUFFER_SIZE + BUFFER_SLACK) 354 | return; 355 | 356 | /* We need to knock BUFFER_SLACK chars off the beginning of the buffer, if 357 | such are conveniently available. (We protect characters that have 358 | never been sent in an update.) */ 359 | 360 | cnum = dwin->numchars - BUFFER_SIZE; 361 | if (cnum > dwin->updatemark) 362 | cnum = dwin->updatemark; 363 | 364 | /* Back up to the previous newline. */ 365 | while (cnum > 0 && dwin->chars[cnum-1] != '\n') 366 | cnum--; 367 | if (cnum <= 0) 368 | return; 369 | 370 | /* Find the first stylerun that we will save. */ 371 | snum = find_style_by_pos(dwin, cnum); 372 | 373 | /* Find the first special that we will save. (Perhaps none.) */ 374 | specnum = dwin->numspecials; 375 | if (dwin->numspecials > 0) { 376 | for (rx=snum; rxnumruns; rx++) { 377 | if (dwin->runs[rx].specialnum >= 0) { 378 | specnum = dwin->runs[rx].specialnum; 379 | break; 380 | } 381 | } 382 | } 383 | 384 | /* trim chars */ 385 | 386 | if (dwin->numchars > cnum) 387 | memmove(dwin->chars, &(dwin->chars[cnum]), 388 | (dwin->numchars - cnum) * sizeof(glui32)); 389 | dwin->numchars -= cnum; 390 | 391 | /* We already know that updatemark >= cnum. */ 392 | dwin->updatemark -= cnum; 393 | 394 | /* trim specials */ 395 | 396 | for (px=0; pxspecials[px]); 398 | if (dwin->numspecials > specnum) 399 | memmove(dwin->specials, &(dwin->specials[specnum]), 400 | (dwin->numspecials - specnum) * sizeof(data_specialspan_t *)); 401 | dwin->numspecials -= specnum; 402 | 403 | /* trim runs */ 404 | 405 | if (snum >= dwin->numruns) { 406 | short sstyle = dwin->runs[snum].style; 407 | glui32 slink = dwin->runs[snum].hyperlink; 408 | dwin->runs[0].style = sstyle; 409 | dwin->runs[0].hyperlink = slink; 410 | dwin->runs[0].pos = 0; 411 | dwin->runs[0].specialnum = -1; 412 | dwin->numruns = 1; 413 | } 414 | else { 415 | for (rx=snum; rxnumruns; rx++) { 416 | tbrun_t *srun2 = &(dwin->runs[rx]); 417 | if (srun2->pos >= cnum) 418 | srun2->pos -= cnum; 419 | else 420 | srun2->pos = 0; 421 | if (srun2->specialnum >= 0) { 422 | if (srun2->specialnum >= specnum) 423 | srun2->specialnum -= specnum; 424 | else 425 | srun2->specialnum = -1; 426 | } 427 | } 428 | memmove(dwin->runs, &(dwin->runs[snum]), 429 | (dwin->numruns - snum) * sizeof(tbrun_t)); 430 | dwin->numruns -= snum; 431 | } 432 | 433 | } 434 | 435 | /* Prepare the window for line input. */ 436 | void win_textbuffer_init_line(window_t *win, void *buf, int unicode, 437 | int maxlen, int initlen) 438 | { 439 | window_textbuffer_t *dwin = win->data; 440 | 441 | dwin->inbuf = buf; 442 | dwin->inunicode = unicode; 443 | dwin->inmax = maxlen; 444 | dwin->incurpos = initlen; 445 | dwin->inecho = win->echo_line_input; 446 | dwin->intermkeys = win->terminate_line_input; 447 | dwin->origstyle = win->style; 448 | dwin->orighyperlink = win->hyperlink; 449 | win->style = style_Input; 450 | win->hyperlink = 0; 451 | set_last_run(dwin, win->style, 0); 452 | 453 | if (gli_register_arr) { 454 | char *typedesc = (dwin->inunicode ? "&+#!Iu" : "&+#!Cn"); 455 | dwin->inarrayrock = (*gli_register_arr)(dwin->inbuf, maxlen, typedesc); 456 | } 457 | } 458 | 459 | void win_textbuffer_prepare_input(window_t *win, glui32 *buf, glui32 len) 460 | { 461 | window_textbuffer_t *dwin = win->data; 462 | int ix; 463 | 464 | if (!dwin->inbuf) 465 | return; 466 | 467 | if (len > dwin->inmax) 468 | len = dwin->inmax; 469 | 470 | dwin->incurpos = len; 471 | 472 | if (!dwin->inunicode) { 473 | char *inbuf = ((char *)dwin->inbuf); 474 | for (ix=0; ix= 0 && ch < 256)) 477 | ch = '?'; 478 | inbuf[ix] = ch; 479 | } 480 | } 481 | else { 482 | glui32 *inbuf = ((glui32 *)dwin->inbuf); 483 | for (ix=0; ixdata; 497 | 498 | if (!dwin->inbuf) 499 | return; 500 | 501 | inbuf = dwin->inbuf; 502 | inmax = dwin->inmax; 503 | inarrayrock = dwin->inarrayrock; 504 | inunicode = dwin->inunicode; 505 | inecho = dwin->inecho; 506 | 507 | len = dwin->incurpos; 508 | if (inecho && win->echostr) { 509 | if (!inunicode) 510 | gli_stream_echo_line(win->echostr, (char *)inbuf, len); 511 | else 512 | gli_stream_echo_line_uni(win->echostr, (glui32 *)inbuf, len); 513 | } 514 | 515 | if (inecho) { 516 | /* Add the typed text to the buffer. */ 517 | int ix; 518 | if (!inunicode) { 519 | for (ix=0; ixstyle = dwin->origstyle; 533 | win->hyperlink = dwin->orighyperlink; 534 | set_last_run(dwin, win->style, win->hyperlink); 535 | 536 | /* ### set termkey */ 537 | 538 | gli_event_store(evtype_LineInput, win, len, termkey); 539 | win->line_request = FALSE; 540 | dwin->inbuf = NULL; 541 | dwin->incurpos = 0; 542 | dwin->inmax = 0; 543 | dwin->inecho = FALSE; 544 | dwin->intermkeys = 0; 545 | 546 | if (inecho) 547 | win_textbuffer_putchar(win, '\n'); 548 | 549 | if (gli_unregister_arr) { 550 | char *typedesc = (inunicode ? "&+#!Iu" : "&+#!Cn"); 551 | (*gli_unregister_arr)(inbuf, inmax, typedesc, inarrayrock); 552 | } 553 | } 554 | 555 | /* Abort line input, storing whatever's been typed so far. */ 556 | void win_textbuffer_cancel_line(window_t *win, event_t *ev) 557 | { 558 | long len; 559 | void *inbuf; 560 | int inmax, inunicode, inecho; 561 | gidispatch_rock_t inarrayrock; 562 | window_textbuffer_t *dwin = win->data; 563 | 564 | if (!dwin->inbuf) 565 | return; 566 | 567 | inbuf = dwin->inbuf; 568 | inmax = dwin->inmax; 569 | inarrayrock = dwin->inarrayrock; 570 | inunicode = dwin->inunicode; 571 | inecho = dwin->inecho; 572 | 573 | len = dwin->incurpos; 574 | if (inecho && win->echostr) { 575 | if (!inunicode) 576 | gli_stream_echo_line(win->echostr, (char *)inbuf, len); 577 | else 578 | gli_stream_echo_line_uni(win->echostr, (glui32 *)inbuf, len); 579 | } 580 | 581 | if (inecho) { 582 | /* Add the typed text to the buffer. */ 583 | int ix; 584 | if (!inunicode) { 585 | for (ix=0; ixstyle = dwin->origstyle; 599 | win->hyperlink = dwin->orighyperlink; 600 | set_last_run(dwin, win->style, win->hyperlink); 601 | 602 | ev->type = evtype_LineInput; 603 | ev->win = win; 604 | ev->val1 = len; 605 | 606 | win->line_request = FALSE; 607 | dwin->inbuf = NULL; 608 | dwin->incurpos = 0; 609 | dwin->inmax = 0; 610 | dwin->inecho = FALSE; 611 | dwin->intermkeys = 0; 612 | 613 | if (inecho) 614 | win_textbuffer_putchar(win, '\n'); 615 | 616 | if (gli_unregister_arr) { 617 | char *typedesc = (inunicode ? "&+#!Iu" : "&+#!Cn"); 618 | (*gli_unregister_arr)(inbuf, inmax, typedesc, inarrayrock); 619 | } 620 | } 621 | 622 | --------------------------------------------------------------------------------