Transvision Detrication for Quarks
Draft, 2Aug99 for Sula PrimeriX II 0.09.2a

See changelog for latest changes.
The most updated versions of this document will be found on one of the following sites:
- http://members.xoom.com/sprimerix
- http://www.geocities.com/SiliconValley/Bit/1962


Sula PrimeriX Programming Guide , Part I

- Using Scheme: Guile, the GNU extension language -

There are currently two different ways of extending Sula Primerix and of writing scripts.
I. Using internal programming
This applies to the client release which is obtained by enabling guile support before compiling. You have to be familiar with the Scheme programming language in order to understand this.
II. Through external programming (named connections)
No knowledge of a particular programming language is required. You use any language that can read from standard input and write to standard output. It's like CGI scripting.

This is Part I of the programming guide. It describes how to program and extend SPX using Scheme. Part II describes named connections.



Table of contents
 0. Intro
 1. Executing Scheme code: Guile consoles
1.1 Using the Control window
1.2 The code editor
1.3 Using a Channel window

2. Programming with Scheme
 2.0 Terms and definitions
 2.1 Programmer's procedures
 2.2 Global variables

0. Intro

Sula Primerix includes support for Guile, the GNU Ubiquitous Intelligent Language Extension. Guile is a Scheme implementation. See http.//www.red-bean.com/guile  for information about the Guile project. Brielfy, Guile is an interpreter for the Scheme programming language which goes beyond the rather austere language presented in R4RS, extending it with a module system, full access to POSIX system calls, networking support, multiple threads, dynamic linking, a foreign function call interface, powerful string processing, and many other features needed for programming in the real world. End of quote.
All features of Guile are accessible both from within Sula Primerix and from within scripts thereof.

DISCLAIMER
The Scheme scriptlets and examples used in this documentation are NOT intended to demonstrate good Scheme programming techniques. Some examples may even contain errors. Please send any corections to fotang@techie.com. 2/1999.

1. Running Scheme code : Guile consoles

There are three ways of directly executing Scheme code. String and numeric results are always displayed immediately. Others also are, if verbose_return is set on. Otherwise, use the procedure say or gs-write.

1.1 Using the Control window

Everything typed in the control window is considered to be Scheme code and is sent to Guile interpreter. Fire up SPX and try out the following ( "==>" indicates the result of the computation).

(+ 1 2)
==> 3
(strerror 100)
==> Network is down
(bind-key "Escape" (lambda(w k s) (gs-hide-console(gs-console-visible?))))
(server "irc.eskimo.com")

1.2 The code editor

A simple text editor is available where program code can be typed, edited and executed. Click on the appropriate icon in the control window or in a channel window.

1.3 Using a Channel window

Again, there are two ways of doing it:

2. Programming using Scheme

The user has the full arsenal of Guile procedures at his/her disposal. In addition, a number of procedures are provided which enable access to Sula internal data and which can be used in the development of scripts and new features.
To reduce name space pollution, most of these procedures are prefixed with "gs-".

2.0 Terms and definitions

  1. The window number

  2. Every window has a number. Each window title begins with a number, usually in square brackets '[]'. That is the window number. If, for example, the title of a window is "[0] No server connection", then the window number is 0.
    An invalid window number (e.g. -1) stands for the control window.
    Each server to which you are connected or to which you were connected is associated with window numbers. The same holds true for DCC chat connections.
    Most procedures require a window number as argument. This makes it possible to find out from which window or from which server a message came. [Applies only to the procedure gs-exec: If no window number is provided, or if the number is invalid, the first window found will be used. The search for windows begins with windows that have a server connection, followed by those not connected to any server.]
    Some procedures consider an invalid number to refer to the control window
  3. The Channel Object

  4. The channel object contains information about a channel.
  5. The Server Object

  6. A server object contains details about a server. The information includes the socket descriptor for the connection to the server, your irc name, number of bytes read from or written to the server, your away status, the server's nickname group, the server alias and so on.
    The server alias is the name of the server as was passed to the SERVER command. This is not always the same as the server name. For example, you may wish to connect to irc.dal.net, but end up on something like voyager.CA.us.dal.net. "irc.dal.net" is the alias while "voyager.CA.us.dal.net" is the server name.
  7. The User object

  8. The user object contains information about a user on a channel (nick, irc name, etc.).
  9. The DCC chat object

  10. A DCC chat object might contain the following information:
    - the number of the window on which chat is taking place
    - the number of bytes read/written to/from the DCC connection
    - the socket descriptor for the connection
    - the nickname of the other side
    - etc.

2.1 Programmer's procedures

These are procedures through which the user can access and manipulate SPX's internal data structures. Some of the examples used here are either from the scripts in examples/scheme or from scripts/scheme directories.
Available procedures have been roughly divided into the following groups.
  1.  Command handling
  2.  Access to Sula commands
  3.  Hooks
  4.  Ignore predicates
  5.  Named connections
  6.  Accesing/Creating SET variables
  7.  Adding other input sources
  8.  Timers
  9.  Windowing
  10.  Configuring certain widgets
  11.  SkriptX
  12.  Defining new colours
  13.  Server objects
  14.  Channels and channel objects
  15.  DCC connections (obsolete: See Changelog.)
  16.  Channel users
  17.  Nickname groups
  18.  Notify and notify groups
  19. Key binding
  20.  Configuration data
  21.  Miscellaneous/Dirty procedures

1. Command handling

(gs-command? name)
Returns true if name is currently a valid client command or alias, otherwise #f.

(gs-ctcp-command? name)
Returns true if <name> is currently a valid client CTCP command, else false.

(gs-new-command name description proc)
(gs-exec "/alias alias_name (procedure_name \"$1-\"$_)")
(gs-delete-command name)

gs-new-command creates a new client command name. New commands may be created at any time. description is a brief description of the command (see commands APROPOS and WHATIS).
proc is the Scheme procedure to be bound to the command. proc should take two arguments:


Another way of creating new commands is to create an alias which is bound to a Scheme routine. We recall that an alias simply contains a text which is expanded and executed each time the alias is used. It dos not matter what the text contains; it just has to be anything that the user may type into the client.
As an example, let's define a procedure foo which converts its input to uppercase and then sends the result to the current target (channel, query or DCC chat).

(define (foo text w) (gs-execute w "msg * " (string-upcase! text)))

Next, we bind this procedure to an alias "up":

(gs-exec "/alias up (foo \"$1-\" $_)")

[This is equivalent to manually typing /alias up (foo "$1-" $_).]

Now,
   /up This is all CAPS!
will be expanded to
   (foo "This is all CAPS!" 0)
thereby sending the messsage "THIS IS ALL CAPS!" to whomever the current target is. $1- expands to user input starting from the second word; $_ expands to the number of the window from which the command came.



All commands should accept the option 'help'. That is, the user should be able to get some help on the command by either typing "command_name -help" or "/help command_name".

gs-delete-command removes the named command. Apart from built-in commands, all commands may be removed. Built-in commands cannot be removed. This really does not matter since it is possible to have multiple versions of a command running at the same time. That will be explained in a while.
gs-delete-command returns #f on failure and true if successful.

Example 0
/alias cd (chdir "$1-")(gs-echo (string-append "Current dir set to " (getcwd)) $_)

Now,
   /cd /tmp
expands to
   (chdir "/tmp")(gs-echo (string-append "Current dir set to " (getcwd)) 0)

if typed in window 1.

Example 1
;; Use /n+ to switch to next nickname
;; Use /n- to switch to previous nickname
(gs-exec "alias n+ (gs-exec \"nick +\" $_)")
(gs-exec "alias n- (gs-exec \"nick -\" $_)")

Example 2
(define (my_command_handler m window)
   (let ((command_name (next-word m 0)))
       (cond
       ((string-ci=? command_name "foo") (display "kung-fu!\n"))
       ((string-ci=? command_name "bar")
           (if(and (not(string-null? (next-word m 1)))
                       (string-ci=? "-help" (next-word m 1))) ;; "/help bar"
              (display "help for bar: help help!\n")
              (display "Free beer for the world!!\n")))
       (else (display (string-append command_name ": something else\n"))))))
(gs-new-command "foo" "foo foo foo" my_command_handler)
(gs-new-command "bar" "ha bar blab bah" my_command_handler)
(gs-new-command "junk" "junk donk junk" my_command_handler)

We have just created 3 new commands foo, bar and junk. Only bar offers help:
/help bar ==> "help for bar: help help!"
Four different instances of a command may exist simultaneously:
  • 1 alias
  • 1 script defined
  • 1 built-in
  • 1 externally defined (via named connection)
When the command is used, a search is performed in the following order:

alias -> script-defined -> externally-defined -> built-in

Particular versions of a command may be excluded from the search by placing "exclusion" characters before the command name. Any number of exclusion characters may be given. The following exclusion characters are defined:
/ use the built-in version
; exclude script-defined commands
? exclude aliases
, exclude externally defined commands

Assuming the command character to be "/", type the following and see what happens!
 /alias echo /echo Coming to you via the alias '$0': $1-
 /(gs-new-command "echo" "script echo..." (lambda(m w) (gs-execute w
    "/;echo Coming 2u via SCM: " (string-rest m 1))))
 /echo This is an alias.
 /?echo This isn't an alias.
 /;echo This is not script defined.
 /?;echo This is neither the alias nor the SCM version.
 //echo This is the built-in echo.

You can still define aliases and commands named "/", ";", "?", or ",". Example:
/alias ? echo $1-? What a question!

Warning Be careful not to define a command or an alias in terms of itself. That would lead to an infinite loop. This is a No-no:
/alias msg msg ...
while these are ok:
/alias msg ?msg ...
/alias msg /msg ...



(gs-new-ctcp-command name description proc)
(gs-delete-ctcp-command name)

gs-new-ctcp-command is the same as (gs-new-command) except that it creates a CTCP command instead of a normal client command.
gs-delete-ctcp-command removes a CTCP command. Built-in CTCP command may only be replaced; they cannot be removed completely.

It is probably superfluous to offer procedures for creating new CTCP commands. Instead, it may be more elegant to set hooks on CTCP_ALL. The procedure gs-new-ctcp-command will probably be removed in the future.

Example 0

(gs-new-ctcp-command "OOPS" "ooops, heaven help you!" (lambda(m w)
   (define sender (next-word m 1))
   (define nick (sender2nick sender))
   (if(not(gs-set? 'verbose_ctcp))
      (gs-echo (string-append "Got a CTCP oops from " sender "!") w 1))
   (gs-execute w "/reply " nick " OOPS Enough is enough!")))
Now, if jjj!lll@mmm.nn types "/ctcp your_nick oops" and verbose_ctcp is /SET off, you'll see this:

Got a CTCP oops from jjj!lll@mmm.nn!

while they'll see something like the following:

CTCP OOPS reply from your_nick: Enough is enough!

If they typed "/ctcp your_nick help oops", they'd see something like the following:

CTCP HELP reply from your_nick: ooops, heaven help you!

Example 1

(gs-new-ctcp-command "CWD" "Sends my cwd" (lambda(m w)
   (define sender (gs-next-string m 2))
   (define nick (sender2nick sender))
   (if(not(gs-set? 'verbose_ctcp))
      (gs-echo (string-append "Got a CTCP cwd from " sender) w 1))
   (gs-execute w "reply " nick " CWD " (getcwd))))

2. Access to client commands

(gs-exec string [window])
(gs-execute window obj1 obj2 ...)

gs-exec executes the client command contained in string as if the user had actually typed it in a window. If no window is given, a value is chosen which depends on the command being executed. Commands that send something to an IRC server look for the first connected window. Others use the first window found, while a command such as SET uses -1.
gs-execute is a wrapper for gs-exec. It's arguments are a list of objects which will be printed into a string and then passed on to gs-exec.
Trouble with the xforms version of SPX:
The client commands SERVER and ACHAT should not be used to initiate connections by executing them via gs-exec, unless there is at least one existing channel window. The result would probably be a Bus error. The reason is that the commands SERVER and ACHAT create windows if none are available. Routines that create windows never return (they loop on forever, receiving and working X events).
Solution: Use the procedure server to connect to servers. The procedure is defined in sula-boot and described under the Miscellaneous section further below.

Example 0
(gs-exec "kick #test xtz no reason!")
(gs-exec "msg * hello there!" 0)

Example 1
(let loop((x 5))
  (gs-execute 0 "msg * (x,sin(x)) =(" x "," (sin x) ")")
  (if (> x 0) (loop (- x 1))))

This sends sine values to the current target (channel, query nick or DCC) on window 1:
<sciz> (x,sin(x)) =(5,-0.958924274663138)
<sciz> (x,sin(x)) =(4,-0.756802495307928)
<sciz> (x,sin(x)) =(3,0.141120008059867)
<sciz> (x,sin(x)) =(2,0.909297426825682)
<sciz> (x,sin(x)) =(1,0.841470984807897)
<sciz> (x,sin(x)) =(0,0.0)

3. Hooks

There is a separate document for hooks (hooks.html).

4. Ignore predicates

(gs-ignored? person what)

Returns #t if person is current being ignored for message type what, otherwise #f. Use "/help IGNORE" to get information on how to use the IGNORE command.

person is any pattern, e.g. ds!bla@weee. It may contain shell wildcards (*, ?, [] etc).
what is a string or symbol representing a message type. The following mesasge types are documented in the IGNORE help file:
Ignore type Definition
'abs absolutely ignored (everything imaginable)
'all all message types and DCC requests (CTCP still allowed)
'ctcp_all all CTCPs (ping, time etc.)
'everythang same as 'abs
'dcc DCC requests(chat,file send,...)
'invites channel invitations
'msg private messages
'misc nick, mode, topic changes by the person, plus all else (kicks etc.)
'notes what are notes?? The NOTE command, probably
'pubmsg channel messages from the person
'pubnotices channel notices

Examples:
(gs-exec "ignore *!*@*.com dcc invites")
(gs-ignored? "fooz!*weezy@techie.com" 'dcc) ==> #t
(gs-ignored? "fooz!*weezy@techie.com" 'msg) ==> #f

5. Named connections

(gs-run name program)
(gs-connect name server port [optional string])
(gs-nc-list)
(gs-nc id)

These procedures create and handle named connections. A named connection is either a network connection, or a connection to an executable on your computer. See part II of the programming guide
(Programming-2.txt) for details about named connections.
gs-run and gs-connect are Scheme implementations of the commands RUN and CONNECT respectively.

gs-nc-list returns a list of the indices (ID's) of all named connections.
gs-nc takes an index and returns a list containing information about the connection (id fd name type other).
type is either 'tcp/ip,'exec, or  'unknown.
name is the name of the connection.
if type is 'exec, other is a list with 2 elements: (PID program).
If type is 'tcp/ip, other is a list with 4 elements: remote_host port IP arguments.
arguments contains the optional string that was passed to the RUN command or to gs-connect. arguments is #f is no optional string was used.

Examples
(gs-run "foooooo" "/home/tano/bin/bots/decy.pl")
(gs-connect "fserv" "jupiter" 6200)
(gs-nc-list) ==> (1 0)
(gs-nc 0)==> (0 8 "fserv" tcp/ip ("jupiter" 6200 "194.153.256.06" #f))
(gs-nc 1)==> (1 7 "foooooo" exec (11266 "/home/tano/bin/bots/decy.pl"))
             (id fd name type PID program)

Related hook types:

See the hook documentation for details on all 3 hook types.

6. Access to SET variables

These are extended definitions of the SET command. /Help SET for more.

(gs-set? name)
(gs-set! name value)

gs-set? returns the value of the built-in SET variable. #f is returned if there is no such variable. #f is also returned if a Boolean variable is currently set off.
gs-set! sets the value of the built-in SET variable to the specified value. For Boolean variables, value is either #t or #f; everything else toggles the value.
gs-set? and gs-set! are Scheme implementations of the SET command. They are faster than (gs-exec "set ...")

(gs-remove-variable name)
Removes the named variable. After this, the variable seizes to exist and the SET command returns "no such variable". Built-in variables cannot be removed.

(gs-new-variable type name [proc])
Creates a new variable. The variable will be of type type and will be called name. Type is any of the following: "boolean", "number", "string", or "char". There is no difference between the new variable and built-in SET variables, except that variables defined this way may also be removed. variable is stored in the same tree as all built-in variables.
If proc is specified and is not false (#f), then it will be called each time the value of the variable changes. proc should take 3 arguments:

See examples/new-variable.scm for examples.
Examples:

(gs-new-variable 'string "email-address")
(gs-new-variable 'boolean 'anti-idle)
(gs-set! 'anti-idle #t)
(display (gs-set? 'finger))(newline)
(display "Userinfo: ")(display(gs-set? 'userinfo))
(write-line (gs-set? 'fmt_other_part))
(gs-set! 'finger_reply "Go finger your very own self. $X over and out.")
(gs-set! 'autochat #f)
(gs-set! 'tmout_tips 500)

7. Adding other input sources

The user can add descriptors to the main select loop and specify routines to be called whenever data is read. The descriptors may refer to network connections, pipes, open files etc.

(gs-add-io-watch fd proc)
This adds the file/socket descriptor fd to the main select loop.
(gs-stop-io-watch fd)
Removes the descriptor from the select loop. The client stops watching it for data.

proc is the procedure to call whenever data is read from the connection. proc should take 3 arguments:

The value of len should always be checked first:
    len = 0 => connection was closed.
    len < 0 => error occurred while reading from the connection.
    len > 0 => the data size in bytes.
After len <= 0, Sula stops watching the fd automatically. It is then up to you to close the socket, pipe or whatever the fd was based on, as well as the descriptor.
THIS API SHOULD BE USED INSTEAD OF GUILE's select.

A silly example: Let's create an IRC client within the main client (read-only)

(define ircserver "irc.gimp.org")(define ircport 6667)(define addr (car(hostent:addr-list (gethostbyname ircserver))))
(define sock (socket AF_INET SOCK_STREAM 0))
(connect sock AF_INET addr ircport)
(send sock "NICK xxxzzz\n")
(send sock (string-append "USER " (getenv "USER") " bla.blah.zz me.yabadoo.com :Go to hell\n"))
(define fd (port->fdes sock))
(define (sub-irc fd data len)
  (cond
    ((= len 0)
      (display "connection closed\n")
      (close sock))
    ((< len 0)
      (display "read error\n")
      (close sock))
    ((string-ci=? "PING" (next-word data 0))
      (send sock (string-append "PONG " (next-word data 1) "\n")))
    (else
      (case (string->number (next-word data 1))
        ((432 433 437) (send sock (display2string "NICK x_y_z" (gs-srandom) "\n")))
        (else (display data))))))
(gs-add-io-watch fd sub-irc)

8. Timers

(gs-alarm 'ref <numeric-id> <seconds> <proc>)
(gs-alarm <seconds> <proc>)
(gs-clock 'ref <ID> <list> <proc>)
(gs-clock <list> <proc>)
(gs-delete-alarm <ID>)
(gs-delete-clock <ID>)
(gs-timer-list)

Examples
(gs-alarm 30 do-something)
(gs-clock 'ref 200 '(20 15 30 20 12 1998) do-something)
(gs-clock '(20 15) do-something)

gs-alarm and gs-clock are used for setting timers. Timers are used for carrying out tasks at specific intervals (alarm), or for performing a task on a specified date (clock). A timer is removed when it expires.

If no reference number (ID) is given, a unique negative ID will be computed and assigned. Both procedures return the ID of the timer just created.

gs-delete-clock and gs-delete-alarm remove all timers having the specified ID.
gs-timer-list returns a list of pairs. The car of each pair is the ID of a timer and the cdr, #t or #f. #t means that the ID refers to an alarm; #f denotes a clock.

gs-alarm

When an alarm expires after so many <seconds>, the procedure proc is called. proc is a Scheme procedure that takes two arguments:

gs-clock

list contains the date on which proc should be called (hour minute second day month year).
proc is a Scheme procedure taking two arguments: Note that the object that is passed as argument to proc after clock expires has a different format from the list that was passed to gs-clock: (second minute hour day month year) instead of (hour minute second day month year).
One must not necessarily supply all members of <list> when setting a clock. However, elements must be supplied from left to right. The remaining values will be filled up with values obtained from the current time. Examples:
(gs-clock 'ref 3 '(19 06) (lambda(foo bar)....
==> clock is set for 19:06:00 today.
(gs-clock '(19 06 20 13) do_my_thing)
==> will do_my_thing at 19:06:20 on the 13th of this month
(gs-clock '(19 06 20 13 11 1998) do_my_thing)
==> cloct set for 19:06:20 on Nov. 13 1998

Examples
- Display some message every 20 seconds:

(gs-alarm 0 (lambda(ref at)
  (display "starting auto message...\n")
  (define (alrm_set nr)
    (gs-alarm 'ref nr 20 (lambda(no t)
    (display (gs-display "Alarm number " no " expired at " t))
    (alrm_set no))))
  (alrm_set ref)))
- When kicked off a channel, wait 10 seconds before rejoining channel.
See scripts/autorejoin.scm.

- Shutdown client at 22:30:15 today:
(gs-clock '(22 30 15) (lambda(ref arg) (gs-exit 0)))

9. Windowing

(gs-window-list)

Returns a list of all existing windows.
Example: /(write-line (gs-window-list)) ==> (1 0)

(gs-create-window)

Create and display a new window. Return value is unspecified.

(gs-kill-window window force)
(force-window-kill window)

Shut down a window. If force is #f, hooks set on WINDOW_DESTROY will not be checked.
force-window-kill is a convenience procedure defined as (gs-kill-windowwindow #t).

(window-count)

Returns the total number of existing channel windows.

(window-valid? window)

Returns #t if window is a valid window number.

(gs-window-server [window])
(gs-window-server2 window)
These procedures return a server object representing the server with which window is associated. Returns an empty list (or null? ?) if no server is associated with the window.
gs-window-server2 is faster than gs-window-server. However, gs-window-server2 returns just 3 components of the server object: the socket descriptor, the nickname, and the away status. The values of other components are not defined.

11. Configuring some widget

(gs-set-window-bg-pixmap str_filename int_window [1])
(gs-set-window-bg-colour str_colspec  window_number [1])
(gs-set-window-fg-colour str_colspec window_number [1])
(gs-set-window-text-font str_fontspec window_number [1])
These change the background pixmaps, the back- and foreground colours and the font of a the text area in a window. window_number is the number of the channel window. if window_number==-1 then the console window is assumed. Otherwise if number isnt a valid window number, #f is returned and nothing happens. If a non-zero number is given as a third argument, then the lower text area of channel window is used.

To remove the background image, speciffy an empty string "" as image name.

gs-set-input-bg-colour str_colour int_window
gs-set-input-fg-colour str_colour int_window
gs-set-input-font str_font int_window
These change values of the input line of a channel window. If the window number is is -1, they operate on the input line of the console window.

gs-set-status-bg-pixmap str_file int_window
gs-set-status-bg-colour str_colour int_window
gs-set-status-font str_font int_window
gs-set-status-fg-colour str_colour int_window
These procedures set the back/foreground and font of the status line of the given channel window.

gs-set-cclock-font str_font
gs-set-cclock-fg-colour str_colour
gs-set-cclock-bg-colour str_colour
These procedures configure the console clock.

All functions returm #f on failure on #t on success.

Examples
Type the following and see what happens...

(gs-set-cclock-bg-colour "brown")
(gs-create-window)
(gs-set-window-bg-colour "blue" 0)
(gs-set-status-bg-pixmap "conf/pixmaps/sky.xpm" 0 1)
(gs-set-input-bg-colour "rgbi:0.48/0.48/0.58" -1)
(gs-set-status-font "-*-terminator two-medium-r-*-*-*-*-*-*-*-*-*-*" 0)

11. Defining and using colours

The back- and foreground colours of a piece of text are specified using @Cx and Dx, respectively.  x is a positive integer representing the index of the colour you wish to use. x has to be a valid index, although any number will return a colour. Different colours and fonts are useful in defining how different kinds of messages look. [TODO: document fonts too]
Example: /echo @C4Hello @D1there. @iOh @C7well!
produes the follwing display:
Hello there! Oh well!

Colour indices 0-15 are already defined when client starts up:
 
Index colour name/spec
0 black
1 red
2 green
3 yellow
4 blue
5 magenta
6 cyan
7 white
8 rgb:40/130/78 (slateblue)
9 rgb:69/69/69 (dimgray)
10 rgb:5f/9e/a0 (cadet blue)
11 rgb:98/fb/98 (pale green)
12 rosybrown
13 light pink
14 plum
15 mediumpurple

To allocate a new colour, use the folowing procedure:

(gs-parse-colour str_colour_spec) str_colour_spec is a valid colour specification , e.g. "blue" or rgb:0/0/ff or rgbi:0/0/1.0.

gs-parse-colour returns the index of the newly allocated colour, or #f if the colour could not be parsed or allocated. The returned index is always greated then 15.
Example:
  spx> (define c (gs-parse-colour "rgb:fa/54/6d"))
  spx> (say "@C"c"testing..")

A <a
href="http://members.xoom.com/sprimerix/scripts/colours.scm">simple script</a> is available which displays all currently defined colours and their indices in all possible background/foreground combinations. The procedure to use is print-colours. See $libdir/scripts for a copy of the scripts.
 

12. Server objects

A server object is returned by the procedure gs-window-server.  Such an object may be passed as argument to the following procedures.

server:fd - procedure
Returns the socket descriptor for the connection to the server.

server:connected? - predicate
Returns #t if a connection to the server exists, else #f.

server:nick - procedure
Returns a string containing your nickname on the server.

server:irc-name - procedure
Returns a string containing your IRC name.

server:modes - procedure
Returns your server modes in a string.

server:alias - procedure
Returns the name of the server as was passed to the SERVER command.

server:name - procedure
Returns the name of the server.

Example:
Upon connecting to a server, check to see whether it's the server to which you wanted to connect.

(gs-on "#001" 123 "*" (lambda(m w)
  (define server (gs-window-server w))
  (if(not(string-ci=? (server:name server) (server:alias server)))
     (gs-message "You landed on a different server..." w))))
 

server:port - procedure

server:last-invite - procedure

Returns a string containing the name of the channel on this server to which you were last invited.

server:away? -predicate
Returns #t if you're currently marked as being away, else #f.

server:read - procedure
Returns the number of bytes read from server so far.

server:wrote - procedure
Returns the number of bytes written to server so far.

server:nickgroup - procedure
The number of the nickname group bound to the server.

server:nickgroup! server number
Changes server nickname group for server object server to number n.

server:start-time - procedure
Returns the time at which the last connection to server was established. This is the number of seconds since 1970 or whatever.

Other server specific procedures
(servers-socket-fd)
Returns a list of pairs (server-socket-descriptor . your-nick-on-the-server) for all server connections. The socket descriptor is whole number; your nick is of course a string.

(gs-raw-write fd string)
This procedure writes the string directly to the file or socket descriptor fd, thereby bypassing any parsing by the client. This may come in handy for quickly quoting to a server/DCC chat connection.

??? alternatively, but probably slower and less elegant,
(define port (fdopen fd "w"))
(send port string)
???

12. Channels and channel objects

(gs-current-channel [<window>])
Returns the name of the current channel. Since one may have many channels on a window, this procedure tells you which channel is in foreground. Returns #f if no channel is available.
Example:
/(gs-current-channel 0)
==>#test
^C (ctrl-c to cycle through channels)
/(gs-current-channel 0)
==>#44th

(channel-last-joined [window])
(channel-last-parted [window])
Each returns a pair (<name> . <key>) describing channel last joined or last left, respectively.

(gs-server-channels [<server name> [<port>]])
Returns a list of channels you are in. Server name defaults to all servers. Port defaults to all ports; a negative value will be taken to mean the default IRC port, usually 6667.
The value returned by the procedure has the following format:
((channel1 server1 port1)(channel2 server2 port2) ...)

This procedure is redundant since the same information can be obtained by looking at the window list.

(gs-window-channels [window])

Returns a list of channel objects representing the channels on the window. Also see (gs-channel).

(gs-channel [channel-name [window]])
Takes the name of a channel and returns a channel object describing the channel.
Example:
(define test (gs-channel "#test"))
(define help (gs-channel "#help" 1))

(gs-channel2 channel-name window)
This is a faster procedure than gs-channel. However, gs-channel2 returns only 2 components of the channel object: channel modes and the channel key. All other components are set to #f. Also, you must supply both channel name and window number.

A channel object may be passed to the following procedures.

channel:name
Returns a string. This is the channel name. It is the name of the channel when it was created. You may be on "#test" but the real channel name might be "#TeSt".

channel:server
On which server is the channel? This procedure returns a server object. See (gs-window-server).

channel:window
Returns an integer. That is the window hosting the channel.

channel:modes
Returns a string containing channel modes.

channel:op?
Returns #t if you are channel op, else #f.

channel:voice?
Can you speak on the channel? Returns #t if so, else #f.

channel:limit
Returns channel limit as integer.

channel:key
Returns a string containing the channel key. #f is returned if no key is set.

channel:topic
Returns the channel topic.

(update-user-list channel)
Updates info about people on this channel. This usually is not necessary. Equivalent to /WHO -update.

13. DCC Objects

(gs-window-dcc [window])
Returns a list of DCC chats on a window. The following procedures accept a DCC chat object and return a selected component.

dcc:fd
Socket descriptor for connection. The descriptor can also be passed as argument to gs-raw-write.
dcc:window
The window hosting the chat.
dcc:nick
The nick to which DCC was established.
dcc:IP
Their address in dotted numeric form.
dcc:read
The number of bytes written to the connection.
dcc:written
Number of bytes read.

14. Channel user object

(gs-channel-nicks channel_name window)
Returns a list of nicknames of all the people in the named channel on a window.

(gs-channel-users)
(gs-channel-users channel_name)
(gs-channel-users channel_name window)

This procedure returns lists of user objects representing people in the channel. Each of the following procedures accepts a user object.

user:window
The number of the window having the channel.
user:channel
Channel name.
user:nick
User's nick.
user:modes
Their channel mode. I don't know what this should be. It is currently either "H" or "H@".
user:address
The user@host.domain string for the person.
user:irc-name
The person's IRC name. If the value is #f, it is unknown. Update user list and try again (using, for example, either the command "/WHO -update" or the procedure update-user-list).

15. Nickname groups

(Also see the NICK command.)
(gs-create-nickgroup <label>)
(gs-addto-nickgroup <group> <nick>)
(gs-list-nickgroups)
(gs-nickgroup <group>)
(gs-server-nickgroup! <server> <group>)

gs-create-nickgroup creates a nick name group with the name <label>. <label> is a string. This procedure returns the ID of the group that was created. This ID is a small positive whole number.
gs-addto-nickgroup adds a nickname to nickname group with ID <group>.
gs-list-nickgroups returns a list of pairs representing all nickname groups. The car of the pair is the ID. The cdr is the group name (label) as was given to gs-create-nickgroup.
gs-nickgroup returns a list object containing the nicknames in a group.
gs-server-nickgroup! changes the nickname group for a server object. This procedure is the same as the procedure server:nickgroup!.

Nickname groups allow the user to define groups of nicknames and bind them to servers. Each group is a doubly linked circular list which can be traversed using "/nick +" and "/nick -". See "/help nick".
Nickname groups can be saved or loaded using the NICK command: /nick -load, /nick -save.

Example of how to create nickname groups
;; create a nickname group and add these nicks to it:
;; "xoOMm", "speedo", "wham", "bamX"
 

(let* ((group (gs-create-nickgroup "Test nick group!")))
  (for-each
    (lambda(nick) (gs-addto-nickgroup group nick))
   '("xoOMm" "speedo" "wham" "bamX"))))
Now,

(display (gs-list-nickgroups))(newline)
==> ((1 Test nick group!)(0 default))
(gs-nickgroup 1)
==> (xoOMm speedo wham bamX mtf)

[TODO: add delete group]

16. Notify groups

(Also see the NOTIFY command.)
The notify feature warns you whenever a particular nickname signs on or off. An extended approach is used here. Some explanation is in  order.
We create different collections of nicknames. Each collection is called a named notify group (similar to nickname groups) and contains names for which we may want to notified when they sign on or off. Each notify group is identified by a unique name. Now we may attach as many groups as we wish to each of the servers to which we are connected. We shall be notified whenever anybody who signs off or on happens to be in a notify group that is attached to the particular server.
Notify groups may be created, removed, saved, edited and so on. The client command for this is NOTIFY. See /help notify.
Hooks may be set on NOTIFY. Hook types: NOTIFY_SIGNON and NOTIFY_SIGNOFF. These are explained in the hook documentation.
The following Scheme procedures are available.

gs-create-notify-group <name>
gs-notify-name->id <name>
gs-notify-id->name <id>
gs-delete-notify-group <id>
gs-add-to-notify-group <id> <nickname>
gs-delete-from-notify-group <id> <nickname>
gs-list-notify-groups
gs-query-notify-group <id>
gs-add-server-notify-group <server_object> <id>
gs-drop-server-notify-group <server_object> <id>
gs-list-server-notify-groups <server_object>

Most procedures accept an id. This is the ID of a notify group: each notify group is represented by a numeric index.
gs-create-notify-group creates a new notify group called <name>. The group is initially empty. The procedure returns the ID of the new group, or #f if an error occured. If the group already existed, the ID is returned.

gs-notify-name->id returns the ID of a notify group.

gs-notify-id->name returns the name associated with an ID.
gs-delete-notify-group removes a group from memory.

gs-add-to-notify-group adds the given name to the notify group. If the given name is already in the list, command is ignored.

gs-delete-from-notify-group removes the given name from a notify group. If the name isn't present in the group, command is ignored.

gs-list-notify-groups returns a list of pairs, each pair representing a notify group. The car field of the pair is the ID of the group; the cdr field is the name of the group.

gs-query-notify-group returns a list of pairs. Each pair stands for a person in the notify group. The car field holds the name of the person. The cdr field contains either #f or #t. #t means that the person is currently marked online; #f means they're off-line. You may force an update at any time using the NOTIFY command: (gs-exec "notify --force")

gs-add-server-notify-group attaches the notify group to a server object.

gs-drop-server-notify-group is the opposite of gs-add-server-notify-group.

gs-list-server-notify-groups returns the list of all notify groups that are attached to a server.  Each group information is the same as the information returned by gs-query-notify-group. This is the general format:
(group1 group2 group3 ... groupi ... groupn)
where
groupi := (<id> (<nicki0> #t|#f)(<nicki1> #t|#f) ...(<nickin> #t|#f))

Saving and loading notify groups to/from files
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Use (gs-exec "notify ...").

17. KEY BINDING

Key binding allows the binding of keyboard and mouse sequences to user defined actions and routines. There is both a high and a low level interface available.

17.1. The low level key binding interface

(gs-bind-key <name> <modifier> [<procedure>])
(gs-delete-key-binding <name> <modifier>)
(gs-query-key-bindings)

gs-bind-key binds a key combination to an action. <name> is the name of the key to which we want to bind a procedure. The name is as defined in /usr/X11/include/X11/keysymdef.h, but with the prefix "XK_" removed as described in the manual for XKeysymToString(3).
Examples of valid key names are "a" for XK_a, "space" for XK_space and "F1" for XK_F1.
<modifier> is an integer. It is a combination of zero or more key masks:

modifier := 0 [ | <keymask_0> [ | <keymask_1>] ... [ | <keymask_n>]]

keymask_0, keymask_1, keymask_n are defined in /usr/X11/include/X11/X.h. Each represents a bit in <modifier> for each of the special keys Shift, Lock, Control etc., as well as for each mouse button.
The following table shows a selection of key masks from /usr/X11/include/X11/X.h.
 
Key Label Value
(None) 0
Shift ShiftMask 1
Lock LockMask 2
Control ControlMask 4
Mod1 Mod1Mask 8
Mod2 Mod2Mask 16
Button1 Button1Mask 256
Button2 Button2Mask 512
Button3 Button3mask 1024

If <procedure> is given, it should be a Scheme procedure taking 3 arguments:

If <procedure> returns a value other than #f, the key event will be considered consumed. Otherwise, the client will continue with normal key processing as if nothing had happened.
You can also inhibit the use of a key combination by either using #f in place of <procedure>, or by not providing any procedure at all. That effectively blocks all uses of a key/mouse combination.

gs-delete-key-binding deletes any key binding on <name> with the given modifier.
gs-query-key-bindings returns a list of pairs. The car field of each pair is the name of a key as described under gs-bind-key; the cdr field contains the value of a modifier as was passed to gs-bind-key.

Examples
 (gs-bind-key "i" 12 (lambda(w m c) ;; 12=(Mod1Mask|ControlMask)
   (gs-set-input "You pressed Alt+Ctrl+i" w)))
 (gs-bind-key "space" 512 (lambda(win mod name)
   (display "You pressed space while holding down Mouse button2\n") #f))
 (gs-delete-key-binding "space" 512)


Worth noting
When binding capital alphabetic letters, you *may* have to include the ShiftMask and/or the LockMask in the modifier since it may not be possible for you get the capital letter without hitting the SHIFT or the LOCK key. I say *may* because it all depends on how you set up your keyboard.
Also, binding "shift+p" is not the same thing as binding "P": "shift+p" is keysym XK_p with modifier ShiftMask. "P" is XK_P with no modifiers.

Related programs, files and documentation
xev(1), xkeycaps(1), xmodmap(1)
 ~/.Xmodmap, /usr/X11R6/lib/X11/Xmodmap, ~/.xinitrc
/usr/X11/include/X11/X.h, /usr/X11/include/X11/keysymdef.h
XStringToKeysym(3), XLookupString(3)

17.1. The High level key binding interface: The key binding script

A user-friendly interface to the primitive procedures described above is available.
Usage:
  (bind-key str_keymask [proc | #f])
  (delete-key-binding str_keymask)
  (list-key-bindings)
  (key-binding->str binding)  ;; binding::=(keysym . state)
  (string->key-binding str_keymask)
  
 Examples
 ---------
There is a hotkey script in scripts/hotkeys.scm. Fire up SPX, type the
following, and see the walls closing in on you...

The following binding causes the key combination Ctrl+i to insert
the string "hello world" into the input line of the window that has
keyboard focus:

  (bind-key "Alt+i" (lambda(window char state)(gs-set-input "hello world" window)))

Completely deactivate the 'z' key:
   (bind-key "z")

Alt+Ctrl+q prompts for confirmation to shutdown client:
  
  (bind-key "Alt+Control+q" (lambda(a b c)(gs-exec "exit")))

Use Ctrl+l to clear channel window:
  /(bind-key "ctrl+l" (lambda(w k s)(gs-execute w "/window --clear 1")))

First mouse button plus left Shift key does a thing:
  
  (bind-key "Button1+Shift_L" (lambda(window char mask)
  	(gs-echo "You pressed button1+shift_left" window)))

Bind Ctrl-n to nick++ :
  
  (bind-key "control+n" (lambda(w junk knuj)(gs-exec "nick +" w)))

Delete key binding "Alt+i":

 (delete-key-binding "Alt+i")
 
Return the string corresponding to a key binding:

 (key-binding->str '(q 517)) ==>"SHIFT+CONTROL+BUTTON2+q"

Given the string representation, return the pair: 
 (string->key-binding "SHIFT+CONTROL+BUTTON2+q") ==>(q . 517)
 
 Arguments to bind-key
 --------------------
 (bind-key key-mask procedure)
 
key-mask is a combination of zero or more modifiers, followed by the name
of a key.
    	    key-mask ::= modifier+* key_name  

modifier is the name of any of the special keys Shift, Control, Mod1 to Mod5,
as well as the mouse buttons 1 to 5. A modifier may or may not given. It's
name is case-insensitive.
The following are valid modifier names, each of them corresponding to a key mask
defined in /usr/X11/include/X11/X.h;
 the suffix "Mask" is not needed.

		Name of modifier	    	Value in $XROOT/include/X11/X.h
		SHIFT     	    	    	ShiftMask
		LOCK	    	    	    	LockMask
		CONTROL   	    	    	ControlMask
		CTRL	    	    	    	ControlMask
		MOD1	    	    	    	Mod1Mask  (probably ALT)
		MOD2	    	    	    	Mod2Mask
		MOD3	    	    	    	Mod3Mask
		MOD4	    	    	    	Mod4Mask
		MOD5	    	    	    	Mod5Mask
		BUTTON1   	    	    	Button1Mask
		BUTTON2   	    	    	Button2Mask
		BUTTON3   	    	    	Button3Mask
		BUTTON4   	    	    	Button4Mask
		BUTTON5   	    	    	Button5Mask
									
procedure is exactly the same as the procedure assigned to gs-bind.
If procedure is #f, or if it is not given at all, then the key sequence
is deactivated; the user will not be able to use it anymore.

 Related programs, files and documentation
 ----------------------------------
 xev(1), xkeycaps(1), xmodmap(1),
 ~/.Xmodmap, /usr/X11R6/lib/X11/Xmodmap, ~/.xinitrc,
 /usr/X11/include/X11/X.h, /usr/X11/include/X11/keysymdef.h
 XStringToKeysym(3)

18. Configuration data

gs-client-name returns the official name of the client.
gs-version, gs-major-version, gs-minor-version, and gs-revision return strings describing version number, or major or minor or revision version numbers, respectively.
gs-release returns the release date.

       (gs-client-name) => "Sula PrimeriX"
       (gs-version) => "0.7.10"
       (gs-major-version) => "0"
       (gs-minor-version) => "7"
       (gs-revision) => "10"
       (gs-release) => 922929504

*sula-home* The name of the directory being used as your personal directory, typically ~/.sula.

*sula-libdir* The name of the system-wide directory for this session, usually /usr/local/lib/sula.

*sula-infosite*
This contains sources of information about the client.

*sula-contact*
Contact e-mail addresses.

19. MISCELLANEOUS/DIRTY PROCEDURES

(server servername [port [nick [password]]])
Connect to a server. port defaults to 6667. nick defaults to (getenv "IRCNICK") or to (getenv "USER") if the former is not set. This procedure is provided for use instead of (gs-exec "server ...")s.
Example:
(server "irc.dal.net" 7000)

(connect-and-join servername port | #f channel [key])
Connect to a server and automatically join the given channel. If port is FALSE, a default is used. If you are already connected to the server, we just join the channel.

Example: (connect-and-join "irc.undernet.org" #f "#irchelp")

(gs-exit <exit_code>)
Quit program, returning exit_code to the shell.

(gs-bell [<percent>])
Ring keyboard bell. <Percent> is the volume. It defaults to 0, the default keyboard setting. <Percent> ranges from -100 to 100. -100 is the minimum volume (off) and 100, the maximum.

Example: (gs-bell 60)

(gs-delay <microseconds>)
gs-delay is similar to the Guile procedure 'sleep' except that gs-delay waits for a number of microseconds instead of seconds. gs-delay returns the value of select(2), or #f if given a bad argument.
Example?
(display "Go wait in a corner...\n")(gs-delay 1500000)(gs-bell)(display "thank you!\n")

(gs-clear-console)
Clear control window.

(gs-hide-console TRUE|FALSE) Hide/Reshow console window.
(gs-window-title window)
Retrieve window title. Use -1 for console.
(gs-window-title! string window)
Changes the title of a window. Use -1 for window to change the title of the console.

(gs-message <string> [<window>])
Displays string on the message bar of a window.
(gs-get-message <string> [<window>])
Returns the message currently displayed on the message bar.

Example:
 (let foo((x 5))
  (define str (gs-get-message 0))
  (gs-message "Hey, Time to wake up..." 0)
  (gs-bell 50)(sleep 1)(gs-message str 0)(gs-delay 500000)
  (if(> x 0)(foo (1- x))))

(gs-set-status <string> [<window>])
 Changes the text on the status bar of a window.
(gs-get-status [<window>])
Returns the current text of the status bar.

Examples:
1. (gs-set-status "-- /help fuer Hilfe --" 0)
2. Append the text "[away]" or "[not away]" to status text if window's
connected to a server
(let((server (gs-window-server 0)))
(if(not (null? server))
   (gs-set-status (string-append (gs-get-status 0)
  (if(not(server:away? server)) " [not away]" " [away]")) 0)))



Misc. String functions
A word is a sequence of one or more non-white characters.

(next-word <string> <n>)
Returns the n'th word in <string>, sans sorrounding white spaces. Indexing begins from 0.
Example:
(next-word " foo ya !" 0); ==> "foo"

(word-index string n)
Returns the start position of word number n.

(word-index "this is ab bad te sst" 3)==>8

(string-rest string n)
Returns string, starting from the (n-1)'th word.
(string-rest " this is ab bad te sst" 3)==>"bad te sst"

(string-start string n)
Returns string up to, and excluding, the nth word.
(string-start " this is ab bad te sst" 3)==>" this is ab "

(gs-display obj1 obj2 ...)
(gs-write obj1 obj2 ...)

Both return a string containing the result of printing the given objects into a string using display and write respectively.
display2string is a synonym for gs-display.
write2string is a synonym for gs-write.

Example:
(gs-echo (gs-display "sin(2) is " (sin 2) " at " (current-time)))
==> sin(2) is 0.909297426825682 at 910730414


Displaying text
(gs-echo string [number [browser]])
Displays a string in a window. If not typed in a channel window, the default window is the console window. If number isn't a valid window number, the console window is used.
(say obj1 obj2 ...)
(_say window_number obj1 obj2 ...)
(_say2 window_number obj1 obj2 ...)
These all display the given objects into a window. say uses the console window. _say uses the upper text area of a channel window, while _say2 displays the given objects in the lower window.

Examples:
(_say2 0 "The sqrt of -1 is " (sqrt -1) " and that's cool...")
==>The sqrt of -1 is 0.0+1.0i and that's cool...
(say sin " is a procedure, and so is " cos)
==>#<procedure sin (z)> is a procedure, and so is #<procedure cos (z)>
(gs-echo "Hey Joe!")
(gs-echo "Hey there" 0 1)


(gs-set-input <text> [<window>])
(gs-get-input [<window>])
(gs-set-input-cursorpos <pos> [<window>])
(gs-get-input-cursorpos [<window>])

gs-set-input replaces the text in the input field of a channel window. To clear an input line, use the empty text "".
gs-get-input returns the contents of the input field of a window.
gs-set-input-cursorpos sets the position of cursor inside the input line.
gs-get-input-cursorpos returns the current position of the cursor in the input line of the specified window.

Examples:
See the script "hotkeys.scm" in the scripts directory for a definition of key bindings which insert any pre-defined information into the input line of a window.

(gs-set-radio <button> [<window>])
(gs-get-radio [<window>])

gs-set-radio activates the radio button <button> on the given window. The effect is as if the user had pressed the button with the mouse.
<button> is the name of one of the radio buttons on a channel window: 'parse, 'query, 'channel, 'chat or 'echo.
gs-get-radio returns the name of the currently active radio button. The name is one of the symbols just listed.

Both gs-set-radio and gs-get-radio return #f on failure.
Note that certain buttons cannot always be set. For example, you can't activate the channel radio button when you are not on any channel. Therefore gs-set-radio should be followed by a call gs-get-radio in order to find out if button was set successfully.

Examples:
(gs-set-radio 'echo 0)
(if(eqv? 'chat (gs-get-radio 0))
(display "input on window 0 currently goes to DCC chat\n"))

gs-handler
This is an error handler for anyone who cares to use it. It prints a nice error report. Using it ensures that Guile doesn't break off processing a piece of code when an error is thrown from one statement. Example:

   ;; ~/.sula/.gsularc
  ...
  (catch #t (lambda() (use-modules (math random))) gs-handler)
  (gs-exec "alias m mode $N $1-")
  (gs-exec "alias t- topic -clear $C")
  ...
If the random number module is not found, the interpreter normally bails out, ignoring all subsequent statements. Using an error handler, it prints an error text and continues processing the rest of the code.

(gs-random [#f])
Returns a pseudo-random using random(3). If any argument is passed to the procedure, tv_usec from gettimeofday(2) is used instead of time(NULL) as seed to srandom(3).
Sula Primerix comes with a random number module for Guile (modules/math/librandom.so) which produces real random numbers. Use the module instead. There's a README in the directory.

(gs-raw-write fd string)
This procedure writes the string directly to the file or socket descriptor fd, thereby bypassing any parsing by SPX. This may come in handy for quickly quoting to a server/DCC chat connection.

2.2. Global variables

The user has access to the following SPX specific variables. Never modify their values!

*sula-random-string*
(string) This is a random word that is generated each time Sula is run. It comprises of the characters a-z,A-Z and 0-9. *Never* modify.

TRUE is #t, FALSE is #f, all is relative.

-- stop Jul 31 1999 --