tkMOO-light

tkMOO-light API Documentation

Programming plugins for the tkMOO-light chat client, an overview with examples.

Tue Mar 16 14:52:58 GMT 1999

This Document is copyright (c) 1998,1999 Andrew Wilson. All rights reserved.

Contents

1.  Introduction
2.  General Notes (move this somewhere else)
3.  General Notes (move this somewhere else)
4.  Namespace Considerations
5.  Client
6.  IO
7.  Plugins
8.  Worlds
9.  Preferences
10.  Requests
11.  Utilities
12.  Window
13.  DB
14.  Fonts
15.  Colours
16.  MCP/2.1
17.  Environment variables

1.   Introduction

the core of tkMOO-light is approximately 5,000 lines of Tcl which provide support for a plugin architecture, configuration file management, a preferences editor, io and user interface.

an additional 10,000 lines of code provide some simple applications that have long been associated with the client, including desktops, whiteboards, a flexible triggers and macro system, logfile control and local editing tools. many of these applications are designed to follow a 'plugin' architecture and could in fact be removed from the main client and be stored in separate Tcl source files.

a plugin architecture is provided which allows new sections of code to be integrated with the client. once integrated the new code receives notification of a range of events from the client's core procedures.

the events handled and dispatched by the client include startup and shutdown phases, notification of the client's connection to or disconnection from a server and notification of receipt of any lines of information from a connection.

any number of plugins can register to receive notification for any of the supported events. notification is normally passed to all registered plugins in turn, and facilities exist which allow a plugin to decline processing of an event, to allow further processing by other plugins or to prevent further processing entirely.

the order in which several registered plugins receive is normally unspecified but a 'priority' value can be associated with the plugin when it registers for receipt of an event. plugins with a high priority are guaranteed to receive notification of an event before any plugins registered with lower priorities. in this way plugins can be 'stacked' and some existing functionality can be overridden by higher priority plugins.

the client is highly configurable and maintains a database of directives which control how the client looks and behaves. directives can be global in nature or can be specific to the different worlds to which the client can connect. this means that the client can be configured differently for each world. directive values can be accessed and updated through the API.

a Preferences Editor UI provides a convenient and safe way to update the inderlying directive database. a value type can be associated with each directive and the UI provides mechanisms to support modification of special types such as fonts, colours, integers with restricted values etc.

the client supports both line-mode and character-mode io over TCP/IP. the API supports opening and closing of connections and the client is able to dispatch these low-level events to registered plugins.

the visible UI is provided by Tk. support for Tk on Windows, Macintosh and UNIX platforms is good and the UI provides routines to simplify the configuration of Tk widgets to present a more platform-consistant look. the core client's main window supports the display of native menus, toolbars and statusbars.

2.   General Notes (move this somewhere else)

the client handles the following events and passes them on to registered plugins. start stop client_connected client_disconnected incoming outgoing plugins can register for events using the client.register procedure. Plugins are short scripts written in the TCL programming language which allow you to customise the client and to add new functionality. when the client connects to a new world it 1 sets the id of the current world (which can be retreived by [worlds.get_current]) 2 calls <plugin>client_connected for all registered plugins

3.   General Notes (move this somewhere else)

4.   Namespace Considerations

all procedures in plugins existing the same namespace as the client's internal routines, so it's possible to unintentionally interfere with existing client procedures or with procedures defined for other plugins. the client doesn't presently support a way to prevent namespace collisions of this form so you need to be careful when defining procedures and global variables.

i currently prefix all procedures and globals associated with a plugin with a unique name. adopting a DNS naming scheme would allow some degree of security. this could result in plugin code resembling:

# use the prefix 'dns_com_awns_' for all our stuff
client.register dns_com_awns_funkyplugin start
proc dns_com_awns_funkyplugin.start {} {
    global dns_com_awns_funkyplugin_foo
    # some code...
    set dns_com_awns_funkyplugin_foo "some string"
}

no 'prefix' policy is enforced by the client at the moment but plugin developers should bare in mind the importance of avoiding any conflicts with the existing client code base.

5.   Client

5.1.  Client API

5.1.   Client API

client.register {plugin event {priority 50}} called by plugins to register their desire to receive matching events. 'plugin' is the prefix used by the plugin code for all its procedures. For example a plugin registers itself for receipt of 'start' events, and provides a procedure to handle the event:

client.register foobar start
proc foobar.start {} {
    # do something...
}

the client will then be able to dispatch 'start' events to:

foobar.start

'event' can take one of the following values:

start 
client_connected 
client_disconnected 
incoming 
outgoing 
stop

'priority' is an integer between 0 and 100 with a default of 50. the client guarantees that a that an event handler with a low priority value will be called before a handler with a higher value. (yes you read it right). this means that if two plugins register handlers for the same event:

client.register foobar start
client.register bazbar start 20

then the one with the lowest priority value, bazbar.start has a priority of 20, will be called before foobar.start, which has a priority of 50. client.incoming event called by io.incoming. 'event' is a unique identifier for an entry in the DB. client will pass the '.incoming' event to all registered plugins. the client's default behaviour is to extract the line of text associated with the event and to display it on the client's main window, ie:

window.displayCR "[db.get $event line]"

client.outgoing line

used to send a line of text to the server. client will pass the '.outgoing' event to all registered plugins. if processing is not halted by a plugin returning [module.module_ok] then the client's default action is to pass the line to 'io.outgoing'.

the client's Triggers environment registers to receive '.outgoing' events and any call to 'client.outgoing' may be processed by any active macros.

client.start

called once, when the client starts up. passes the '.start' event to all registered plugins which then usually perform one-time initialisation of some kind.

client.stop

called once, usually by client.exit. allows for a graceful shutdown of the client and registered plugins. passes the '.stop' event to all registered plugins. useful for flushing buffers, closing logfiles etc.

client.exit

usually called by the 'Connect->Quit' menu option. calls client.stop and destroys the main window, shutting down the client completely.

client.connect_world world

closes any current open connection then attempts to open a connection to the Host and Port with the given world identifier. calls io.connect, failure to connect is handled by io.connect. if connection is successful then a check is made against the Login and Password. if no Login and Password are present for this world and if UseLoginDialog is true then the client displays a login dialog box and returns immediately. if no login dialog box is required then the connection is completed, sending any ConnectionScript to the server. finally the client's default settings for DefaultFont, LocalEcho are asserted.

client.connect {host port}

usually called by the dialog box which is created by the 'Connect->Open' menu option. closes any current open connection then attempts to open a connection to the given host and port. calls io.connect

client.disconnect

usually called by the 'Connect->Close' menu option. sends the DisconnectScript associated with the current world and then calls io.disconnect. clears the current world identifier, so subsequent calls to [worlds.get_current] will return the empty string.

6.   IO

6.1.  IO API

6.1.   IO API

io.outgoing line

used to send a line of text to the server. usually called by client.outgoing. when called directly the line of text is sent to the server immediately without passing through any plugins.

io.connect {host port}

attempts to open a socket to the given 'host' and 'port'. if the socket can be opened then any existing connection is broken, causing the .client_disconnected event then the .client_connected event is sent.

if the socket can't be opened then this causes the .host_unreachable event to be sent. .host_unreachable is only handled by client.host_unreachable at present. it's client.host_unreachable that prints the message:

Server at 127.0.0.1 7777 is unreachable.

the ioconnect procedure also returns 1 to indicate inability to open the socket, or 0 for success.

7.   Plugins

Plugins are .tcl files (or subdirectories containing .tcl files and/or other data). The files are looked for and sourced by the client when it starts up. The action of sourcing the .tcl file causes .register procedures to be invoked. eg:

# my_plugin.tcl
client.register my_plugin start priority
proc my_plugin.start {} {
    puts "Hello World"
}

proc my_plugin.start {} {
    # some code...
}

priority integer between 0 and 100 (default 50)

8.   Worlds

8.1.  The worlds.tkm File
8.2.  Worlds API

8.1.   The worlds.tkm File

The Worlds Definition File describes the sites that the client knows about listing the name, machine host name and port number of each site. An optional username and password can be given for each definition which the client will use to connect you to your player object. The file contains lines of text laid out as follows:

World:    <human readable string for the Connections Menu>
Host:     <host name>
Port:     <port number>
Login:    <username>
Password: <some password>
ConnectScript: <lines of text to send following connection>
ConnectScript: ...
DisconnectScript: <lines of text to send before disconnecting>
DisconnectScript: ...
KeyBindings: <keystroke emulation>
DefaultFont: <font type for main screen, fixedwith or proportional>
LocalEcho: <On | Off>

World:    <a different string for a different world>
Host:     <a different host name>
Port:     <a different port number>
...

The client looks for the worlds.tkm file in each of the following locations depending on the platform you're using, and only data from the first matching file is used by the client:

On UNIX		./.worlds.tkm
		$HOME/.tkMOO-lite/.worlds.tkm
    		$tkmooLibrary/.worlds.tkm

On Macintosh	worlds.tkm
		$env(PREF_FOLDER):worlds.tkm
   		$tkmooLibrary:worlds.tkm

On Windows	.\worlds.tkm
		$HOME\tkmoo\worlds.tkm
    		$tkmooLibrary\worlds.tkm

8.2.   Worlds API

worlds.create_new_world
worlds.copy {world copy}
worlds.delete world
worlds.sync
worlds.load
worlds.save
worlds.worlds
worlds.get { world key }
worlds.set { world key { value NULL }}
worlds.unset { world key }
worlds.get_generic { hardcoded option optionClass directive {which ""}}
worlds.get_current
worlds.match_world expr

return a list of world identifiers for worlds whos Name matches the expression expr.

9.   Preferences

9.1.  Definition list entries
9.2.  Available types
9.3.  Preferences API

the client contains a simple Preferences Editor. the editor manages changes to directive values and provides a simple UI. the procedure 'preferences.register' can be used to associate additional information with any directive, including a type which influences how the directive is to be displayed and updated, a default value and a descriptive string of text to display in the editor window. 'preferecnes.register' takes 3 parameters, a providor, a category and a definition list.

the 'providor' is a string associated with the logical block of code that supports the directive, for example the plugin prefix. the client doesn't currently make use of this parameter but may do in the future. the preferences editor groups directives by Categories, at present the 'category' parameter is only used to determine on which page of the Editor the directive should be placed. add directives in the same Category will appear on the same page of the Editor.

the 'definition_list' is a list of records, one per directive. each record contains a list of enties. each entry is a keyword followed by any number of elements.

The following example shows the registration of the directive 'UseModuleMCP21'. The directive is supplied by the MCP/2.1 plugin which uses the prefix 'mcp21' for all its procedures. The directive is displayed on the "Out of Band" page of the Preferences Editor.

The directive is boolean, taking the values "On", "Off". The directive takes the value "On" by default. When displayed in the Editor the directive is represented by a checkbox alongside the legend "Use MCP/2.1".

preferences.register mcp21 {Out of Band} {
    { {directive UseModuleMCP21}
        {type boolean}
        {default On}
        {display "Use MCP/2.1"} }
}

Only a directive's name and value are stored in the worlds.tkm file. The preferences interface is merely a way to associate more information with the directive and to provide a simple way for people to modify directive values, through a GUI. Not all directives need to be accesible via the Preferences Editor and directive values can still be modified using the 'worlds.get' and 'worlds.set' calls directly. It's perfectly acceptible for a plugin to rely upon a directive for which no preferences informatino has been registered.

9.1.   Definition list entries

{directive <unique directive name>
{type <type>
{default <default value>
{display <text to display in Preferences Editor>
{choices <list of choices acceptable>

9.2.   Available types

boolean

a checkbutton

choice-radio

a radiobutton. a button is displayed alongside each of the available choices. click to select one of the available choices.

updown-integer

an entry widget with adjacent up/down arrow buttons. text typed into the widget is checked for errors and the value corrected to fall within the supported range.

choice-menu

a menu-button. press to select one of the available choices.

string

an entry widget

font

an entry widget with an adjacent [Browse] button. the button activates the Font Chooser. Under Tcl 7.6/Tk 4.2 the [Browse] button is unavailable.

colour

a coloured frame. pressing the frame activates the Colour Chooser.

password

an entry widget. typed characters appear as '*'

text

a text widget. displays 3 lines of normal text, but can contain any number of lines of text.

9.3.   Preferences API

preferences.edit world

opens the Preferences Editor on the world with the id 'world'.

preferences.register providor category definition_list

10.   Requests

10.1.  Request API

the request api is used to store the keyword-value pairs from an out-of-band message. the message may be a single line message or a multiline message. each line in an out-of-band message is given an identifier, the request tag, through which the message's handler can access the keyword-value pairs.

the request tag 'current' can be used to access the keyword-value pairs of single line messages. for example, the following MCP/2.1 single-line message handler prints the value of the 'name' keyword.

proc example.handle_single {} {
    set name [request.get current name]
    puts "the name is $name"
}

the request tag for multiline messages is usually extracted from a tagging parameter given on each of the several lines of the message. MCP/2.1 provides a unique value for the keyword '_data-tag' which is made available to all lines in a multiline message. XMCP/1.1 uses the special keyword 'tag' for the same purpose.

when the last line of a multi-line messages is received the message's handler is invoked and extracts the request tag taken from the tagging parameter. for example, the following MCP/2.1 multiline message handler prints the value of the 'text' keyword.

proc example.handle_multiline {} {
    set request_tag [request.get current _data-tag]
    set text [request.get $request_tag text]
    puts "the text is $text"
}

10.1.   Request API

request.get { tag keyword }

return the value of the keyword associated with the tag'ed message. some protocols allow keywords to be optional, and if no keyword is found for this tag then the command will fail, causing a Tcl error. the following idiom is common:

set data default
catch { set data [request.get $tag data] }
# if request.get failed then 'default' remains as the value of
# the keyword 'data'
request.set { tag keyword value }

set the value of the keyword associated with the tag'ed message. for example XMCP/1.1 multiline messages consist of a header line and subsequent data lines followed by an end-of-message line. the handler for the header line will determine what action should be taken when the entire message has been read and will save a callback procedure name:

request.set current xmcp11_multiline_procedure "xmcp-who*"

the handler for the XMCP/1.1 end-of-message will look for a callback procedure to invoke:

# set up a useful default value
set which current
# get the value of the 'tag' keyword used throughout this
# message
catch { set which [request.get current tag] }
set callback [request.get $which xmcp11_multiline_procedure]
if { $callback != "" } {
    request.set $which _lines [request.get $which xmcp11_lines]
    xmcp11.do_callback_$callback
}   
request.current

returns the tag assigned to the message currently being processed. only suitable for XMCP/1.1 message handling code. a shorthand for:

set which current 
catch { set which [request.get current tag] }
request.create tag

request.destroy tag

request.duplicate { source target }

11.   Utilities

util.use_native_menus

returns 1 if the version of tk supports native menus. native menus are supported in Tk versions 8.0 and better.

util.unique_id token

returns a unique identifier, an integer prefixed by 'token'. the identifier is guaranteed to be unique for a given session, but the integer component is set to 0 when the client starts so it shouldn't be used to generate identifiers which need to be unique across sessions.

util.populate_array { array text }

takes a string containing keyword-value pairs of the following form:

keyword1: value1 keyword2: "value two" keyword3: 1234

and parses the string to provide values for an associative array. The following example parses the string 'str' and reports the contents of the new array.

set str "keyword1: value1 keyword2: \"value two\" keyword3: 1234"
# scrub any previous information
catch { unset my_array }
util.populate_array my_array $str
puts "keyword1=$my_array(keyword1)"
puts "keyword2=$my_array(keyword2)"
puts "keyword3=$my_array(keyword3)"
util.version

returns a string denoting the client's version

util.buildtime

returns a string indicating when the client was built

util.eight

returns 1 if the version of Tcl is version 8.0 or better.

util.slice { list { n 0 } }

returns a list of the n-th elements taken from each element of 'list'. For example:

[util.slice {{1 2} {3 4}} 1] => {2 4}
util.assoc { list key { n 0 } }

returns the first element of 'list' whos own n-th element is 'key'. returns {} if no such element is found. For example:

[util.assoc {{foo 1} {bar 2}} bar] => {bar 2}

12.   Window

window.display {{line ""} {tag ""}}

display the text string 'line' on the client's output window, applying any tags in the list 'tag'. 'tag' is a whitespace delimited string containing Tk Text widget tag names.

window.displayCR {{line ""} {tag ""}}

display the text string 'line' on the client's output window, applying any tags in the list 'tag'. 'tag' is a whitespace delimited string containing Tk Text widget tag names. the text from a subsequent call to window.displayCR or window.display will be placed on the following line.

window.place_nice {this {that ""}}

place the window 'this' with it's top left hand corner offset +50,+50 from the top left hand corner of 'that's window. if 'that' is not given then the window is placed offset +50,+50 from the top left hand corner of the screen.

window.place_absolute {win x y}

place the window 'win' with it's top left hand corner positioned at coordinates 'x' 'y'

window.clear_screen

clears the main client window.

window.menu_preferences_add {text command}

adds a menu item to the client's Preferences menu. the item label is taken from the string 'text' and when invoked the callback defined in 'command' is executed. For example:

window.menu_tools_add "Edit Preferences" preferences.edit
window.menu_preferences_state {text state}

sets the state of the menu item in the Preferences menu bearing the label 'text' to be 'state'. 'state' is one of 'normal', 'disabled'.

window.menu_tools_state "Edit Preferences" normal
window.menu_tools_add {text command}

adds a menu item to the client's Tools menu. the item label is taken from the string 'text' and when invoked the callback defined in 'command' is executed. For example:

window.menu_tools_add "Edit Triggers" edittriggers.edit
window.menu_tools_state {text state}

sets the state of the menu item in the Preferences menu bearing the label 'text' to be 'state'. 'state' is one of 'normal', 'disabled'. For example:

window.menu_tools_state "Edit Triggers" disabled
window.open

creates a dialog box. you can enter host and port number. on pressing the [Connect] button the client will remove the dialog box and attempt to connect to the given site.

window.set_status {text {type decay}}

displays 'text' in the client's status bar message window. long messages will be truncated to fit the window, adding a trailing '...' to the message. messages on the status bar message window decay after 20 seconds unless 'type' has the value 'stick', in which case the message will remain on the message window till it is removed by another message.

window.add_toolbar frame

adds a Tk Frame widget 'frame' to the client's list of toolbars. toolbars appear stacked above the client's output window. a call to 'window.repack' is required to display the new toolbar.

window.remove_toolbar frame

removes the frame 'frame' from the client's list of toolbars. a call to 'window.repack' is required to remove the new toolbar from the display. '.remove_toolbar' does not destroy 'frame'.

window.toolbar_look frame

applys the look of a native toolbar to the Tk Frame widget 'frame'.

window.add_statusbar frame

adds a Tk Frame widget 'frame' to the client's list of statusbars. statusbars appear stacked below the client's input window. a call to 'window.repack' is required to display the new scrollbar.

window.remove_statusbar frame

removes the frame 'frame' from the client's list of statusbars. a call to 'window.repack' is required to remove the new statusbar from the display. '.remove_statusbar' does not destroy 'frame'.

window.create_statusbar_item

creates room for an item on the client's built in status bar. returns the name of the new item. the calling procedure must now create a widget with that new name and call the Tk command 'pack' to place it in the built-in statusbar. For example:

# get the name for a new widget
set newframe [window.create_statusbar_item]
# Create a Tk Frame widget, applying the appropriate styling
frame $newframe -bd 0 -highlightthickness 0 -relief raised
# pack the new frame.  Tk will immediately redisplay the widget
pack $newframe -side right -fill both    
window.delete_statusbar_item item

destroys the widget named 'item'. Tk will immediately remove the widget from the client's built-in statusbar.

window.save_layout

saves the current geometry of the main client window to the 'WindowGeometry'directive and syncs the worlds.tkm file.

window.set_scrollbar_look scrollbar

applies the native look to a scrollbar. removes excess padding round the scrollbar and sets the scrollbar width.

window.iconify

iconifies the main client window

window.deiconify

opens the main client window if it was iconised

window.hidemargin menu

attempts to hide the margin (leading/trailing whitespace) on the last menu item of this menu.

window.focus win

gives focus to the widget 'win'. on Windows platforms the parent window of 'win' will be raised, becoming the active window on the desktop.

13.   DB

13.1.  DB API

Provides a very simple key-value database, or a trivial 'object-id and property' model. db.get and db.set can take multiple arguments assumed to be a chain of links between logical identifiers. The first argument is assumed to be a unique id, the remaining arguments are assumed to be property names. For example:

# create 2 objects
set id1 [util.unique_id id]
set id2 [util.unique_id id]
db.set $id1 next $id2
db.set $id2 message "hello"
db.get $id1 next -> $id2
# chains of references
db.get $id1 next message -> "hello"
# remove reference to $id1
db.drop $id1
db.get $id1 next -> FAILS! $id1 doesn't exist!
# $id2 is not changed...
db.get $id2 message -> "hello"

13.1.   DB API

db.set args db.get args db.drop object

14.   Fonts

14.1.  Fonts API

The client provides procedures to access 5 logical fonts which aim to cover most of the requirements of a typical application. Prior to Tk 8.0 fonts were defined as X11 font strings, with poor results on Windows 95 and Macintosh. When the client runs on Tk 8.0x the client tries to set up some resonable default values for use when the user hasn't provided a preference.

14.1.   Fonts API

fonts.get font a wrapper for the other procedures in the API, font can take any of the following values: fixedwidth, plain, bold, header, italic fonts.fixedwidth fonts.plain fonts.bold fonts.header fonts.italic

15.   Colours

15.1.  Colours API

15.1.   Colours API

colourdb.get colour returns a hexadecimal colour representation. colour can take any of the following values: red, orange, yellow, green, darkgreen, lightblue, blue, darkblue, black, grey, white, pink, magenta, cyan

16.   MCP/2.1

16.1.  MCP/2.1 API
16.2.  Registering messages
16.3.  Retreiving Arguments
16.4.  Testing connection capability
16.5.  Sending messages
16.6.  Dealing with cords
16.7.  MCP/2.1 Argument lists
16.8.  MCP/2.1 API

Further information on the MCP/2.1 protocol specification can be found here: http://www.moo.mud.org/mcp2/

16.1.   MCP/2.1 API

16.2.   Registering messages

To handle an incoming message you will need to register a package and detail the TCL procedures that will handle each message: mcp21.register $package $version $full-message $procedure

# register the 'foo' package, version 1.0, with 2 messages
# 'foo-bar' and 'foo-baz'
mcp21.register foo 1.0 foo-bar my_foo_bar
mcp21.register foo 1.0 foo-baz another_proc

Precisely when you register matters too. mcp21 is a plugin and like other parts of the client it uses the client.register call to initialise itself when the client issues the booting 'start' message. mcp21 needs to be initialised before any calls are made by other plugins to its own .register procedure. mcp21 registers with the default priority of 50, so a calling plugin should use a higher number (60 say) when registering its own .start procedure, which calls mcp21.register when the 'start' message arrives. For example:

# wibble.tcl, an example plugin 
# wait for mcp21 to initialise before registering
client.register wibble start 60
proc wibble.start {} {
    mcp21.register foo 1.0 foo-bar wibble.foo_bar
    mcp21.register foo 1.0 foo-baz wibble.foo_baz 
}

16.3.   Retreiving Arguments

The client's 'request' API lets your handler procedures pick up their arguments. You need to provide a $request_id of the following form. 'current' if the procedure is handling a single-line message. value of '_data-tag' field if the procedure is handling a multi-line message. If in doubt the following construction will always work:

# use the correct request_id
set request_id current
catch { set request_id [request.get current _data-tag] }
# get the message arguments 'blah' and 'wibble'
set blah [request.get $current blah]
set wibble [request.get $current wibble]

request.get $request_id $keyword => value

16.4.   Testing connection capability

# what do server and client have in common.  .report_overlap
# returns a list of {package version} pairs.
set overlap [mcp21.report_overlap]
# pick out the version for the 'dns-com-awns-something' package
set version [util.assoc $overlap dns-com-awns-something]
# does this application want to support this version?
if { ($version == {}) || ([lindex $version 1] != 1.0) } {
   puts "Sorry, only dns-com-awns-something/1.0 spoken here."
   return
}
# continue processing

16.5.   Sending messages

mcp21.server_notify $message {keyval-list}

16.6.   Dealing with cords

cord.open $type => $id | ""
cord.cord $id $message {keyval-list}
cord.close $id

16.7.   MCP/2.1 Argument lists

A keyval-list contains {keyword value} pairs. If the value is a list then a flag should be added as the 3rd element of the list. eg: {mylist {1 2 3} 1}

16.8.   MCP/2.1 API

mcp21.register {package version message callback}

tell the MCP/2.1 plugin that the client can handle MCP/2.1 package 'package' with version 'version'. in addition when the message 'message' is received from the server then the procedure 'callback' is available to handle the message. For example:

# we're supporting version 1.0 of the dns-com-awns-example
# package.  the message 'dns-com-awns-example-foo' is to be
# handled by my_package.foo.  the message 'dns-com-awns-bar'
# is to be handled by my_package.bar
mcp21.register dns-com-awns-example 1.0 \
    dns-com-awns-example-foo my_package.foo
mcp21.register dns-com-awns-example 1.0 \
    dns-com-awns-example-bar my_package.bar

the client is able to use this information to tell the server which packages/versions it is able to understand, as part of the MCP/2.1 negotiation phase.

mcp21.register_internal {module event}

used by a module (another name for a plugin) to register that it should receive events generated by the MCP/2.1 package. the module should provide a specially named procedure which is to receive the event notification.

One internal event is supported at this time: mcp_negotiate_end this event is dispatched immediately following completion of the MCP/2.1 negotiation phase. at this time the MCP/2.1 plugins is guaranteed to know the set of packages supported by the session. for example, the module 'my_package' wishes to be informed when the MCP/2.1 package has completed the MCP/2.1 negotiation phase:

# tell me when the negotiation phase is complete
mcp21.register_internal my_package mcp_negotiate_end
proc my_package.mcp_negotiate_end {} {
    # do something...
}
mcp21.server_notify {message {keyvals {}}}

send the MCP/2.1 message 'message' to the server with the given keyword-value pairs. no keyword-value information is sent by default. no check is made to see if the server understands 'message', so the calling code will need to use the mcp21.report_* procedures to determine the message's suitability.

'keyvals' is a Tcl list, empty by default. each element in the list contains two items, a keyword and a corresponding value. if the value is meant to be a list then a third item must be present with the value '1'. For example:

# the following MCP/2.1 messages are only supported by
# version 1.1 of the dns-com-awns-example package.  check to
# see if this connection can support that version, return
# immediately if we're out of luck
set overlap [mcp21.report_overlap]
set version [util.assoc $overlap dns-com-awns-example]
# if we're in luck version == { dns-com-awns-example 1.1 1.1 }
if { ($version == {}) || ([lindex $version 1] != 1.1) } {
    # we're out of luck, bail out...
    return
}
# send a single line message, no keyvals
mcp21.server_notify dns-com-awns-example-single
# send a single line message, with a simple keyval pair
mcp21.server_notify dns-com-awns-example-simple \
    [list [list foo bar]]
# send a single line message, with several simple keyval pairs
mcp21.server_notify dns-com-awns-example-several \
    [list [list foo bar] [list baz 123]]
# send a multi-line message, the value of keyword 'foo' is a
# list of numbers.  Note the third element with value '1' indicating
# that this data is to be sent in several lines of MCP messages.
mcp21.server_notify dns-com-awns-example-multi \
    [list [list foo [list 6 7 8] 1] [list grr "Hello World"]]
mcp21.report_packages

return a list of all packages registered with the MCP/2.1 plugin by other components withing the client. returns a list of elements with each element of the form: { package min-version max-version }

mcp21.report_server_packages

return a list of all packages which the server states it can support. returns a list of elements with each element of the form: { package min-version max-version }

mcp21.report_overlap

compare the packages made available within the client with those reported by the server and work out the best version of each package that can be understood by both client and server.

returns a list of elements with each element of the form: { package min-version max-version }

17.   Environment variables

On Windows and UNIX the client will search the contents of the directory pointed to by the environment variable TKMOO_LIB_DIR looking for worlds.tkm, triggers.tkm and /plugins/ directory information.

The command-line option '-dir <directory> will override the value in TKMOO_LIB_DIR.