Writing an Interpreter with GlkTerm
When writing a glk plugin, the first thing to do is to get a working interpreter. Follow the instructions in the page on setting up XCode to create a new project. Create your interpreter as a new Foundation tool, then remove all of the source files that XCode creates for you. Link to the GlkClient framework, change any references to “glk.h” to <GlkClient/glk.h>, write a main.m file and then build: you should now have a working interpreter ready to be made into a plug-in.
To test your interpreter, you will need to launch the GlkTerm application. This is an application that will listen for any CocoaGlk intepreters starting up and will offer to act as their UI, and makes it possible to easily debug an interpreter in XCode. With this application running, you should be able to just hit Run in XCode, and you’ll see your interpreter start up.

Writing a main.m file
One of the ways that CocoaGlk diverges slightly from the glk spec is that it requires that you write a main() function. This is because the framework system in OS X does not support having the main() function in an external library. It also saves having a system-specific startup routine that does essentially the same job
All this function needs to do is call cocoaglk_start() with the arguments passed into your interpreter, and then call glk_main(). However, this is not particularly useful for writing a plugin as you will want to know which file that your interpreter should be running and maybe also the save game that you should be loaded. There are two functions you need to know about in order to do this. cocoaglk_get_input_stream() is the first, which will return NULL under GlkTerm but when your interpreter is running under Zoom it will return a stream containing the game that your interpreter should run. The second is cocoaglk_get_stream_for_key(), which will return a stream for a particular key: it’s possible to set as many keys as you like, but the important one is ’savegame’, which will return NULL or a stream containing the savegame that Zoom wants your interpreter to restore (if you can’t support this, don’t worry, you can specify that your interpreter can’t restore saved games on startup).
These functions, along with many more extensions, are defined in the cocoaglk.h header file found in the GlkClient framework. Note that these input streams are very slow for random access, so if your interpreter needs random access, you might want to copy the streams into memory and use a memory stream instead. You may also have an interpreter design that requires it to open a named file at startup: you’ll find the cocoaglk_bind_memory_to_named_file() function useful for getting around this.
Sample main.m code
The following sample is an example of a main function which will read the file to be interpreted and a save game (either from Zoom when running as a plugin or by prompting in GlkTerm), and then launch the interpreter. This code is used to start up the SCARE plugin for running Adrift games, but it is a good basis for creating other interpreters.
Note that this code reads the game file into memory, and then maps it to the filename ‘__GAME__.taf’: this isn’t strictly necessary for SCARE, but it does demonstrate how you could do it for an interpreter that does require a filename.
/*
* cocoaglk.m
* ZoomPlugins
*
* Created by Andrew Hunter on 07/10/2007.
* Copyright 2007 Andrew Hunter. All rights reserved.
*
*/
#include <stdlib.h>
#include <GlkClient/glk.h>
#include <GlkClient/cocoaglk.h>
#include "scare.h"
extern int
gsc_startup_code (strid_t game_stream, strid_t restore_stream,
sc_uint trace_flags, sc_bool enable_debugger,
sc_bool stable_random);
int main(int argv, const char** argc) {
// Get everything running
cocoaglk_start(argv, argc);
// We will be using .taf files
cocoaglk_set_types_for_usage(fileusage_cocoaglk_GameFile, [NSArray arrayWithObjects: @"taf", nil]);
// Get the game file that we'll be using
strid_t gamefile;
gamefile = cocoaglk_get_input_stream();
if (gamefile == NULL) {
frefid_t gameref = glk_fileref_create_by_prompt(fileusage_cocoaglk_GameFile, filemode_Read, 0);
if (gameref == NULL) {
cocoaglk_error("No game file supplied");
exit(1);
}
gamefile = glk_stream_open_file(gameref, filemode_Read, 0);
}
if (gamefile == NULL) {
cocoaglk_error("Failed to open the game file");
exit(1);
}
// Put the file into memory (the stream that CocoaGlk passes to you is often a very slow cross-process stream)
glk_stream_set_position(gamefile, 0, seekmode_End);
int length = glk_stream_get_position(gamefile);
unsigned char* data = malloc(length);
glk_stream_set_position(gamefile, 0, seekmode_Start);
glk_get_buffer_stream(gamefile, (char*)data, length);
cocoaglk_bind_memory_to_named_file(data, length, "___GAME___.taf");
// See if there's any saved games to restore
strid_t savedGame = cocoaglk_get_stream_for_key("savegame");
strid_t restore_stream = NULL;
strid_t game_stream = NULL;
if (savedGame) {
// Load the saved game into memory
glk_stream_set_position(savedGame, 0, seekmode_End);
int saveLength = glk_stream_get_position(savedGame);
unsigned char* saveData = malloc(saveLength);
glk_stream_set_position(savedGame, 0, seekmode_Start);
glk_get_buffer_stream(savedGame, (char*)saveData, saveLength);
restore_stream = glk_stream_open_memory((char*)saveData, saveLength, filemode_Read, 0);
}
// Set up the variables used by the runner
game_stream = glk_stream_open_file(glk_fileref_create_by_name(fileusage_cocoaglk_GameFile, "___GAME___.taf", 0), filemode_Read, 0);
// Run the startup code
gsc_startup_code(game_stream, restore_stream, 0, FALSE, FALSE);
// Pass off control
glk_main();
// Finish up
cocoaglk_flushbuffer("About to finish");
glk_exit();
return 0;
}