Implementing multikit "invoked commands"
An "invoked command" is the command that is executed whenever a program - including a directory - is launched (usually by double-clicking).
The name of this command is determined by the program's "invokedCmd" attribute.
An invoked command can be implemented in one of three ways
- As a built-in command.
The current version of multikit automatically implements three invoked commands
- mk_default_dir_sharer: The default invoked command for directories. It implements the sending and receiving - over the network - of announcements within the directory.
- mk_message: The invoked command that implements text messages - e.g., the "README" notice that's in the "Announcements" directory.
- mk_invoke_user_record: The command that is run whenever
a user's record (in the "multikit users" directory) is double-clicked.
(This built-in implementation does nothing, but it could be overridden by another implementation, if desired - see below.)
- As a separate executable program
- As a script written in the Tcl scripting language
The latter two methods are described below.
Note: A built-in invoked command can be overridden by a Tcl script (or separate executable) for the same invoked command.
Invoked commands as separate executable programs
An invoked command "foo" can be implemented as a program named "foo" (with an appropriate extension - e.g., ".exe" - if you're running Windows).
This program must reside in the same directory as multikit itself.
(This is a security measure, to give the user control over the set of programs that might be executed when a multikit program is invoked.)
This program will be invoked with the following arguments:
- <Program Id>: multikit's identifier for this program. (This can be treated as just an opaque string.)
- <Internet Address>: The program's internet's address in "dotted quad" form (or the string "{}" if the program is not a channel).
- <Port>: The program's port number (or the string "{}" if the program is not a channel).
- <TTL>: The program's TTL (or the string "{}" if the program is not a channel).
- <Key>: The program's encryption key (currently, the string "nokey", or "{}" if the program is not a channel).
- The remaining arguments are attribute pairs: an even number of arguments - the first of each pair being an attribute name, and the second being its value.
(These arguments may be 'quoted' using Tcl's quoting conventions.
E.g., if an attribute value consists of more than one word, then it will be surrounded by curly braces.)
Note that the invoked command for a directory cannot, at present, be implemented as a separate program. Instead, a directory invoked command must be implemented as a Tcl script, as described below.
Invoked commands as Tcl scripts
For examples of these scripts, please see the following files (which were included in the multikit distribution, and should be present in the same directory as "multikit" itself):
- "mk_sdp_medium.tcl".
These scripts are used to launch SDP-announced sessions, by launching the appropriate helper program (e.g., "vat") for the corresponding medium (e.g., "audio").
- "mk_sap_watcher.tcl". This script implements the "SDP default directory", by watching for SAP announcements, and translating them into multikit program ids and attributes.
- "mk_default_dir_sharer.example.tcl". This script is not actually used, but it shows how the default invoked command for directories ("mk_default_dir_sharer") could have been implemented as a script, rather than being built into multikit.
The script file for an invoked command "foo" must be named "foo.tcl", and, again, must reside in the same directory as "multikit" itself.
The invoked command proc
Each script file (for invoked command "foo") must contain a Tcl proc, also named "foo", with the same argument signature as described above:
program id, internet address, port number, ttl, key, attribute-name-1, attribute-value-1, ..., attribute-name-N, attribute-value-N
"proc multikit_input"
If the invoked command is for a directory, then the script file should also contain a proc "multikit_input".
This proc is called by multikit whenever the user adds a new program to the directory
(or deletes a program from the directory).
This proc takes the following arguments:
- <Participation Code>:
A 1-letter code that indicates the way in which the new program should be
'announced' or 'shared' in the directory.
The following codes are currently defined:
- p: Indicates that the new program should be treated
as being 'posted' persistently into the directory.
Specifically, this means that this directory membership should be
announced (over the network) not just by this node, but also
by any other node that receives the announcement.
Thus, as long as at least one other node receives the announcement, it
will continue to take place, even if the original node's multikit is no longer running.
- d:
Indicates that the new program should be treated as being
'displayed' in the directory by this node, whenever it's running, but that its announcement should
not be retransmit by any other node.
Thus, the announcements will stop whenever the original node's multikit is no running.
- x:
Indicates that the announced program is to be deleted from
the directory (so that it will no longer be visible to the user).
(As with the p command, it is expected that other, receiving, nodes
will also reannounce this deletion.)
- <Incarnation>:
A numeric string that indicates the 'freshness' of this announcement.
This number is updated, by multikit, whenever the announcement is changed in some way - e.g., the attributes change, or an 'addition' (p or d participation code) turns into a 'deletion' (x participation code), or vice versa.
Receiving nodes use the 'incarnation' in order to recognize (and reject) stale announcements.
- <TTL>: The TTL that should be used when announcing the new program on this directory. (This is not necessarily the same as the directory's TTL; it can be less, if the new program is a channel.)
- <Directory Id>: The id of the directory being added to (i.e., this one).
- The remaining arguments form a <program description> for the new program.
A <program description> is defined as follows:
- If the new program is a channel (including a directory):
<Program Id> <Parent Id> <Expiration Date> "channel" <Internet Address> <Port> <TTL> <Key> <Attribute Pairs>
- If the new program is a bundle:
<Program Id> <Parent Id> <Expiration Date> "bundle" <member descriptions> <attribute pairs>
where <member descriptions> is a sequence of zero or more
<program description>s for each member of the bundle, each one ending with a "|" argument.
- If the new program is anything else (a non-channel that's not a bundle):
<Program Id> <Parent Id> <Expiration Date> "general" <Attribute Pairs>
Notes:
- <Directory Id>, <Program Id>, and <Parent Id> can be treated just as opaque strings. <Expiration Date> is an integer giving the time (in seconds, origin 1/1/1970 UTC) when the new program will die.
- If the <Participation Code> is x ('deletion'),
then <Attribute Pairs> may be omitted.
Ditto for bundle <member descriptions>.
"proc multikit_cleanup"
If the invoked command is for a directory, then the script file should also contain a proc "multikit_cleanup".
This proc is called by multikit whenever the directory is no longer needed
(e.g., when no windows for the directory are being shown, and multikit
is making no ongoing 'announcements' to the directory).
"multikit_cleanup" should reclaim any state that was previously created by the invoked command.
In particular, if the invoked command had previously created a multicast socket (using "groupsock_create"), then "multikit_cleanup" should close it
(by calling "groupsock_delete").
"multikit_cleanup" takes a single argument:
<Directory Id>
Calling "multikit_output"
If the invoked command for a directory receives information, over the network, that announces a new program, refreshes or updates information about an existing program, or deletes a program (from a directory), then it should call "multikit_output", with arguments:
<Participation Code> <Incarnation> <Directory Id> <program description>,
each as described above.
(multikit will then use this information to update its internal database.)
Notes:
- As a special case, "multikit_output" can also be used to update the attributes
of the directory itself, rather than a program within the directory.
To do this, use the string SELF as the <Program Id> parameter.
- See the file "mk_default_dir_sharer.example.tcl" for an example of the "multikit_input" and "multikit_cleanup" procs, and a call to "multikit_output".
Additional commands available to scripts
In addition to the usual Tcl commands, the following special commands are also available for use by invoked command scripts:
Querying the state of the local "multikit"
- multikit_getSharingParams <Directory Id> <Program Id>
This routine is used to check whether a program is still a member of this directory, and if so, what are its current attributes.
If <Program Id> is no longer a member of <Directory Id>, then the call returns the empty string, otherwise the call returns a list in the same form as the arguments to "multikit_input" described above.
Notes:
- As with "multikit_output", it is possible to query the attributes of the
directory (rather than those of a program within the directory) by using
the string SELF as the <Program Id> parameter.
- See the file "mk_default_dir_sharer.example.tcl" for an example of a call to "multikit_getSharingParams".
- multikit_isProgram <Program Id>
Returns 1 if <Program Id> is a currently valid program, otherwise 0.
- multikit_isChannel <Program Id>
- multikit_isDirectory <Program Id>
- multikit_isBundle <Program Id>
Returns 1 if <Program Id> is a
"channel",
"directory"
or
"bundle", respectively, otherwise 0.
- multikit_isMember <Program Id>, <Directory Id>
Returns 1 if <Program Id> is a member of a directory (or bundle)
<Directory Id>, otherwise 0.
- multikit_parent <Program Id>
Returns the program id of the 'parent' (in the
attribute inheritance hierarchy) of the program
<Program Id>, or {} if <Program Id> is a 'template' program, or is
not a valid program.
- multikit_expirationDate <Program Id>
Returns the expiration date of the program <Program Id>, or the
largest possible integer if <Program Id> is not a valid program..
(The value
returned is an integer. Use the "clock format" command to convert
it to a human-readable date.)
- multikit_getAttribute <Program Id> <Attribute Name>
Returns the value of the attribute <Attribute Name> of the program
<Program Id>, or {} if no such attribute exists (or if <Program Id> is not a valid program).
- multikit_listAllAttributes <Program Id>
Returns a list - in the form
{attrName1 attrValue1 attrName2 attrValue2 attrName3 attrValue3 ...} - of
the names & values of all of the attributes of <Program Id>,
or {} if <Program Id> is not a valid program.
- multikit_getGroupEId <Program Id>
Returns the 'GroupEId' of the channel <Program Id>,
or {} if <Program Id> is not a channel.
The returned string is a list of the form
{<IP Address> <Port> {<TTL> <Key>}}.
- multikit_listMembers <Program Id>
Returns a list containing all members of the directory or bundle
<Program Id>, or {} if <Program Id>
has no members, or is not a directory or bundle.
- multikit_getClipboard
Returns the program id of the most recent item that
the user has 'cut' or 'copied' using the GUI,
or {} if this clipboard is empty.
(This routine will typically be used to implement
customized directory views.)
Updating the state of the local "multikit"
- multikit_output
See the description above.
- multikit_removeMember <Directory Id> <Program Id>
Removes, from (the local copy of) the specified directory, a program that was previously added
to the directory using multikit_output
Note: If you wish to share a deletion with other nodes on the network,
rather than just performing it locally,
then don't call multikit_removeMember.
Instead, call multikit_output, with a
<Participation Code> of x.
- multikit_setClipboard <Program Id>
Sets multikit's clipboard to
<Program Id>.
Thus, this program will be used if the user next does
a "paste" operation using the GUI.
(This routine will typically be used to implement
customized directory views.)
- multikit_launchProgram <Program Id>
<askAboutStartTime> <canOpenBundles>
Invokes the program <Program Id>, as if it were
launched from the GUI by double-clicking.
<askAboutStartTime>
and
<canOpenBundles>
are optional Boolean parameters
(default values: 1 and 0, respectively).
If <askAboutStartTime> is set, multikit will prompt the user if the
program to be launched has a start time that occurs in the future.
If <canOpenBundles> is set, and
<Program Id> is a 'bundle',
then the program will not be launched.
Instead, a new view will be created, containing the contents of the bundle,
in the same way as if the user had <control>-double-clicked it.
(This routine will typically be used to implement
customized directory views.)
- multikit_deleteView <token>
(See the description of
customized directory views
below.)
- multikit_exit
Shuts down multikit.
Miscellaneous routines
- multikit_getHostId
Returns (in 'dotted quad' form) the IP address of the local host.
- multikit_getNameAndVersion
Returns a string that shows the name and version of this application, e.g., "multikit v1.0a49"
- multikit_generateNewProgramId
Returns a newly-generated program id - i.e., one that's not currently being
used by a program.
- multikit_localFile <fileName>
Returns the absolute path name of <fileName>,
as if it were a file in the same directory as the multikit executable.
In other words, the name <fileName> is treated as being
relative to the directory in which multikit resides, not the current
directory (should this be different).
- multikit_initTk <windowTitle>
Makes the full "Tk" windowing toolkit available to this invoked command script,
and creates an initial top-level window (".") with title <windowTitle>.
(<windowTitle> is optional. If it's omitted, the new window is
titled "tk" instead.)
- multikit_createRTPgroupsocks <IP Multicast Address> <Port> <TTL>
- multikit_deleteRTPgroupsocks <socket0> <socket1>
These routines are variants of groupsock_create and
groupsock_delete (see below).
They can be used to more efficiently manage
the socket pairs that implement RTP/RTCP connections.
(For an example of the use of these commands, see the "mk_sdp_video.tcl" invoked command script.)
- ipAddrDotted2Integer <Dotted Quad>
- ipAddrInteger2Dotted <Integer>
These routines convert an IP address between the 'dotted quad' form and an
integer.
- randomBetween <M>, <N>
Takes two integers <M> and <N> as arguments, and returns a pseudo-random integer in the range
<M> through <N>-1.
- processExists <pid>
Returns 1 if the process <pid> (e.g., as returned by the "exec"
command) exists, otherwise returns 0.
- tk_messageBox
This is the same "tk_messageBox" routine that's in the Tk toolkit.
It's made available to invoked command scripts so that they can pop up simple warning or error messages, without having to call
multikit_initTk
to load the entire Tk toolkit.
The "groupsock" library
This is a simple library for multicast datagram communication.
The following new Tcl commands can be called from an invoked command script:
- groupsock_create <IP Multicast Address> <Port> <TTL>
Opens a datagram socket on the specified multicast address (in 'dotted quad' form) and port, and
returns a corresponding Tcl open file (socket). <TTL> indicates the
default TTL to use when sending to this multicast address.
To receive a source-specific multicast ("SSM") session,
replace <IP Multicast Address> with
<IP Multicast Address>,<IP Source Address>
(The <TTL> parameter must still be present, even
though this socket will be effectively read-only. It is used
to specify the TTL used for received packets that have been delivered
via a UMTP tunnel
(if any).)
- groupsock_read <socket> <offset> <binaryDataResult> <sourceAddress>
Blocks until data arrives on the specified multicast socket
(which should have been created using "groupsock_create").
This command returns the read data. (This data is
assumed to be a Tcl string, so only
characters up to and including the first '\0' are returned.)
<offset> is an optional parameter (default value: 0) which
specifies the byte offset, within each packet, of text data.
(This is useful if the packets start with a binary header
that you don't care about.)
<binaryDataResult> - if present - is the name of a
variable whose value, on return from this command, will be a list of <offset> elements, each representing a byte of binary data from the start of the packet.
(This data is in "network order".)
Each element in the list is a string of the form "0xhh", denoting a hexidecimal integer
in the range [0x00, 0xFF].
<sourceAddress> - if present - is the name of a variable whose
value, on return from this command, is the IP address (in 'dotted quad' form)
of the sender of the packet.
- groupsock_write <socket> <data> <TTL> <offset> <binaryDataList>
Sends <data> (a Tcl string) to the multicast address given by <socket>
(which should have been created using "groupsock_create").
This routine returns an empty string.
<TTL> is an optional parameter. If present, it indicates the
TTL to use when sending the data, overriding the
default TTL that was specified when <socket> was originally
created.
<offset> - if present - specifies the byte offset, within the output packet, of the text (<data>).
If <binaryDataList> (described below) is not present, then the
preceding <offset> bytes will be zero.
<binaryDataList> - if present - is a list of entries representing the first
<offset> bytes of the output packet (in "network order").
Each entry must be an integer in the range [0, 255].
If fewer than <offset> entries are present, then the remaining bytes will be
zero. If more than <offset> entries are present, then the extraneous
entries are ignored.
- groupsock_getCreationParams <socket>
Returns a list of three elements -
<IP Multicast Address> <Port> <TTL>
- that were used to create a multicast socket <socket>
(that was previously created using "groupsock_create").
If the socket was not created using "groupsock_create", has been deleted, or does not exist,
then this command returns an error.
- groupsock_turnOnBackgroundReadHandling <socket>
(and groupsock_turnOffBackgroundReadHandling <socket>)
Turns on (or off) background read handling on
a multicast socket <socket>
(that was previously created using "groupsock_create").
This is useful for invoked command scripts that do not read from a multicast
address/port directly, but "exec" separate processes that do.
In this case the invoked command script will want to 'notify' multikit that
this address/port is being used, so that it can be
tunneled if necessary.
It does so by creating a multicast socket for the address/port (using
groupsock_create), then calling
groupsock_turnOnBackgroundReadHandling.
(For an example of this, see the "mk_sdp_video.tcl" invoked command script.)
Note that an invoked command script should not read from a multicast socket
that has had background reading turned on. If it wishes to do so, it
should first call groupsock_turnOffBackgroundReadHandling.
- groupsock_delete <socket>
Closes a multicast socket (that was previously created using "groupsock_create").
If the socket was not created using "groupsock_create", has already been deleted, or does not exist,
then this command has no effect.
- groupsock_initInSlave <interp>
Initializes the "groupsock" library in a slave interpreter
<interp>, making all of the groupsock_*
commands available there.
Example: The following script listens to a multicast group,
printing out data whenever it arrives:
set socket [groupsock_create 224.42.42.42 54321 15]
fileevent $socket readable {puts [groupsock_read $socket]}
Note:
Upon request, LIVE555.COM
can provide special
variants of the Tcl/Tk shells "tclsh" & "wish" that have the
"groupsock" library built in.
(These new shells are called "groupsock_tclsh" and "groupsock_wish".)
Customized directory views
Using an invoked command script for a directory, it is possible to
create your own, customized, view(s) of this directory,
instead of using the views ("itemized" or "positional")
that are built into multikit.
A script for a customized directory view must contain a Tcl proc:
proc multikit_createView {handle} {
...some customized code for showing the view...
}
multikit_createView
is called - by multikit - whenever it requests a new,
customized, view of this directory.
When this new view is later deleted, the script must then call the routine
multikit_deleteView <handle>
where the <handle> parameter is the value that had earlier
been passed to multikit_createView.
(The <handle> is multikit's internal id for the view.)
This call to multikit_deleteView is important.
If it is omitted, multikit will think
that the view is still active, and will remain running even after its
last window has been deleted.
(It is OK, however, to call multikit_exit before deleting
a view. If you do this, then the view will be reinstated automatically
when multikit is restarted.)
A script for a customized directory view can obtain its directory contents
by periodically calling the
multikit_listMembers routine, described earlier.
Notes
- multikit attempts to create a customized view for a directory
if the directory's windowState attribute
contains the pair
viewType customized
Alternatively, the user can change an existing non-customized view into
a customized view using the "View" menu.
- There is currently no way for a directory script
to get immediate notification of
new members, or of deletions. Instead, it must 'poll', using
multikit_listMembers.
Return to the main multikit page