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.
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.
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
you can get input from the server by registering with the 'incoming' event. the client will call <plugin>incoming passing a single parameter, an 'event id'. you use the event id to get hold of the line of text sent by the server.
<plugin>incoming must return one of [modules.module_ok], [modules.module_deferred].
.module_ok will stop further processing of this event by other registered plugins.
.module_deferred will cause the client to pass the same event to subsequent registered plugins.
the client will prerform some cleanups after the event has been processed. the event data is removed.
the '.incoming' handler is also able to modify the event in progress. it's possible to rewrite the line of text in an event and then allow the client to hand the event to the next plugin in the processing chain. for example
# rewrite the line on input... proc foo.incoming event { set line [db.get $event line] # modify the line set line "blah$line" # keep processing this event return [modules.module_deferred] }
the client's Triggers environment also performs rewriting for '.outgoing' events. a line of text is received from the input window and passed to 'client.outgoing' for fruther processing by registered plugins. the Triggers plugin may perform a string substitution on the outgoing line and then call io.outgoing directly to prevent further processing. this is analogous to the following handler:
# rewrite the line on output... proc foo.outgoing line { # modify the line set line "blah$line" # output immmediately io.outgoing $line # stop processing this event return [modules.module_ok] }
out of band protocols are like this, eg: mcp/1.0, mcp/2.1, xmcp/1.1
their .incoming routines detect the relevant out of band message header (eg #$#), decode the message and dispatch to user-specifed handler routines. the client's support for the MCP/2.1 protocol is explained in another section.
plugins generally provide new functionality for use by the client. there are several possible approches to using this architecture including:
redefining internal behaviour by modifying existing procedures in the client. when a plugin is sourced by the wish interpreter 'proc'edure definitions in the sourced file will overwrite any procedures with the same name already in the client.
this approach is very rare (gateway.tcl) and may require plugins to be upgraded each time a new client release change some internal detail.
if you have to do this then it's a sign of a possible defficiency in the API. the client should have provided a simple API to let you provide the same functionality without having to change the client's source code. i'd like to hear about this if possible and hopefully be able to add to the API
provide support for other plugins (eg visual.tcl). extending the client's functionality behind the scenes, relying on other plugins to be made available which make use of this new functionality.
extend API, adding extra commmand to the Triggers environment using edittriggers.register_alias
provide support for Out of Band protocols. both LambdaCore Local Edit and XMCP/1.1 applications rely on the client containing some specially-named procedures to handle an incoming OOB messages. in the absence of such a handler the protocol treats incoming messages as an error. some plugins can contain specially named procedure definitions alone with none of the callback registration code required for more elaborate protocols like MCP/2.1
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.
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]"
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.
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.
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.
usually called by the 'Connect->Quit' menu option. calls client.stop and destroys the main window, shutting down the client completely.
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.
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
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.
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.
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.
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)
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
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.
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.
{directive <unique directive name> {type <type> {default <default value> {display <text to display in Preferences Editor> {choices <list of choices acceptable>
a checkbutton
a radiobutton. a button is displayed alongside each of the available choices. click to select one of the available choices.
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.
a menu-button. press to select one of the available choices.
an entry widget
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.
a coloured frame. pressing the frame activates the Colour Chooser.
an entry widget. typed characters appear as '*'
a text widget. displays 3 lines of normal text, but can contain any number of lines of text.
preferences.edit world
opens the Preferences Editor on the world with the id 'world'.
preferences.register providor category definition_list
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" }
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'
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 }
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] }
returns 1 if the version of tk supports native menus. native menus are supported in Tk versions 8.0 and better.
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.
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)"
returns a string denoting the client's version
returns a string indicating when the client was built
returns 1 if the version of Tcl is version 8.0 or better.
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}
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}
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.
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.
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.
place the window 'win' with it's top left hand corner positioned at coordinates 'x' 'y'
clears the main client window.
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
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
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
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
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.
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.
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.
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'.
applys the look of a native toolbar to the Tk Frame widget '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.
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'.
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
destroys the widget named 'item'. Tk will immediately remove the widget from the client's built-in statusbar.
saves the current geometry of the main client window to the 'WindowGeometry'directive and syncs the worlds.tkm file.
applies the native look to a scrollbar. removes excess padding round the scrollbar and sets the scrollbar width.
iconifies the main client window
opens the main client window if it was iconised
attempts to hide the margin (leading/trailing whitespace) on the last menu item of this menu.
gives focus to the widget 'win'. on Windows platforms the parent window of 'win' will be raised, becoming the active window on the desktop.
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"
db.set args db.get args db.drop object
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.
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
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
Further information on the MCP/2.1 protocol specification can be found here: http://www.moo.mud.org/mcp2/
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 }
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
# 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
mcp21.server_notify $message {keyval-list}
cord.open $type => $id | "" cord.cord $id $message {keyval-list} cord.close $id
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}
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.
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... }
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"]]
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 }
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 }
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 }
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.