Extending adesklets - adesklets 0.6.1
Description: adesklets 0.6.1

Next: Help wanted!, Previous: Programming adesklets, Up: Top

## 6 Using adesklets from other language environments

adesklets was made to be easily usable from a variety of langage environments1. This chapter explains to programmers how the adesklets interpreter hooks itself into the system in a language-neutral fashion.

If you are not minimally used to POSIX systems or do not have basic notions on operating system programming, this chapter is probably of no interest to you.

Please note that the Python package under `scripting/python/adesklets` is a good complement for what you are about to read.

### 6.1 Requirements

If you want to use the adesklets interpreter from your favorite language but it is not supported out of the box2, you can still do it, provided your langague supports one of these features:

- Reading and writing in pipes; or
- Reading and writing from/to files unbuffered

It should also be able to either3:

- Catch IPC reliable signals; or
- Read files in non-blocking mode; or
- Be able to perform a poll/select on files

Finally, it should also be able to start a child process.

Those a pretty light requirements from a POSIX point of view, but let us mention for the sake of completeness that if your language environment does not meet these requirements, you could still use it provided you can get your application to talk reliably with another program4 that meets them. Of course, this is not an ideal situation and it should be avoided as much as possible.

### 6.2 Initialization mechanism

As you probably know (See Using adesklets.), there are two modes of operation for `adesklets`, as it can be called:

1. as a desklets launcher–whenever you call `adesklets` without arguments.
2. as an interpreter–in all other cases (interactive command line use or a child process from a desklet).

#### 6.2.1 adesklets called as a desklets launcher - restarting all desklets

On a new X session, the typical startup sequence is:

1. `adesklets` gets called without arguments, somewhere from the X session initialization scripts.
2. From there, the new adesklets instance acts as a desklets launcher. It parses the `$HOME/.adesklets file`, then forks as many times as there are desklets to start. The original launcher process then exits as there is no need for a daemon here.
3. All forked processes set up the `ADESKLETS_ID` environment variable with a string representation of the proper integer ID, then execute 5 the associated script.
4. Each desklet launches itself an `adesklets` instance as a child process, passing along the `ADESKLETS_ID` variable without altering it. It is the desklet's duty to make sure that:
   - the adesklets interpreter standard streams, `stdin`, `stdout` and `stderr`, get separately redirected, in a way that parent process can easily write to adesklets' `stdin`, and read from `stdout` and `stderr`. Use of pipes, FIFOs or event regular files are all possible.
   - the writes to `stdin` and reads to the two other streams are all unbuffered.
   - the first command argument given to `adesklets` is the absolute file name of the desklet script.
5. Each newly created `adesklets` interpreter process initializes itself using the environment `ADESKLETS_ID` variable and the command line absolute desklet name given as its first argument to lookup its unscriptable characteristics6 from `$HOME/.adesklets`. When this operation is over, the `ready!` event is generated (See Events.), and the interpreter is ready to process commands.

#### 6.2.2 adesklets called as an interpreter - registration of a new desklet

When a new desklet gets called directly, the startup sequence described in the previous subsection is the same, except for the first, second and third steps which simply don't occur. This means that the environment `ADESKLETS_ID` is not set; the new `adesklets` interpreter uses this fact to detect this is a new desklet, parse the `$HOME/.adesklets` configuration file and allocate the first free ID to the desklet.

It should be noted that if the desklet file name given as the first argument to the child `adesklets` instance is relative or is not both readable and executable by the current user, the desklet will not get registered in `$HOME/.adesklets`, and therefore not automatically restarted in subsequent new X sessions by the launcher. In case `stdin` is a terminal7, the registration does not take place either.

#### 6.2.3 Consequences

This way, from the scripted desklet perspective, both cases are identical, and no conditionnal treatment is needed. All the desklet initialization job is synthesized in the fourth point in the subsection above.

If needed later on, the desklet can use the `get_id` command to determine its ID.

### 6.3 Using streams

As explained previously, a desklet communicates with its private adesklets interpreter via redirected unbuffered streams. Here, let us see what information they convey.

#### 6.3.1 stdin: the commands

On the interpreter's `stdin`, the desklet outputs the commands it wants to execute in the order in which it wants them to be executed, one command per line8. Since reading this stream is suspended while a command is processed, a desklet should generally wait for a command to be processed before emitting another9 (See the next section).

The command format is pretty simple: it is a command name, followed by arguments separated by spaces, all in plain text representation, including numbers. adesklets does not care about the number of spaces between arguments, as long as there is at least one. In the special case of commands where the last argument is a string (`text_draw`, for instance), adesklets will choose its first non-space characters to be its beginning; the rest of the line, including spaces, are considered to be part of the string.

Two files, automatically kept in sync with the source (`scripting/prototypes` and `scripting/enums`) from the base source distribution, provide in tab-separated plain text format the description of all the commands and numeric constants that can be made available to the desklets10. A good binding for a specific language should come with some automation to auto-generate pertinent parts of its own codebase based on those files; for instance, the Python binding adds a `protoize` action to its `setup.py` `distutils` script to do so. Ideally, this automation code should be redacted in the target language with a reasonable set of libraries11; if it is not well suited to this task, you need to limit yourself to use:

- Portable Bourne shell, conforming to POSIX 1003.2 and 1003.2a, scripts
- A recent sed (Streaming Editor), compatible with GNU sed12
- Portable use of any bc for arithmetic

if you want your work to become part of the official package.

#### 6.3.2 stdout: the command results

The `adesklets` interpreter frequently polls its `stdin` for new characters, and reads from it as needed. Whenever it receives an End-Of-Line ('\n'), it stops reading from it and tries executing the command. Once the command has been carried out (possibly emitting a few lines on `stdout`), it sends as its last entry on it a status message of the form:

         command RANK ok: MESSAGE_STRING

if the command was successful, or:

         command RANK error: ERROR_STRING

if an error occurred.

`RANK` is the numerical ID of the command (an unsigned integer) that starts at zero when the interpreter is created, and then is automatically incremented by one for every subsquent line it receives. All commands are guaranteed to be processed in the order they were submitted. `ERROR_STRING` is a free-form message in human-readable form explaining the source of the error. `MESSAGE_STRING` is also in human-readable form, but is formatted in such a way that it can be used to retrieve algorithmically useful return values for any commands. Those return values can be:

1. an integer–the return value ID (`create_image` and such)
2. a tuple of integers–the numerical command output (for `context_get_color` and the like)
3. a list of strings–the ordered list of output strings (for `images_info` and the like) for commands that output multiple lines on `stdout`, with the last line being the status message
4. a description string–a one-line textual answer from the command
5. nothing at all

Algorithmically, your "return value retrieving routine" should first try to retrieve integers from `MESSAGE_STRING`13. If there is one or more integers present, it should verify that the message is not a mere copy of an emitted command; if it is, the return value follows the fifth case (see above); otherwise, if only one integer is found, this is the first case; if more than one is found, the second one. If no integers are found and there was a list of lines sent to stdout before the status message of the command, and after the status message of the previous command, it then returns according to third case. In what's left, if the status message is different from the emitted command, it is the fourth case. All remaining conditions should be mapped to the fifth case. This retrieving algorithm is working for all commands listed in `scripting/prototypes`.

#### 6.3.3 stderr: the events

All remaining asynchronous event reports are sent to the interpreter's `stderr`. They are asynchronous regarding the processing of commands; if they will never occur while a command is processed (in the time interval between the submission of the complete command on `stdin` and the output of its message status line on `stdout`), they can be sent at any other time14.

Event reports are single lines of the form15:

         event: EVENT_MESSAGE

In a stylized Backus Naur form, the grammar for EVENT_MESSAGE would be:

    EVENT_MESSAGE :: ready! |
                     backgroundgrab |
                     menufire MENU_ID MENU_STR |
                     motionnotify X Y |
                     enternotify X Y |
                     leavenotify X Y |
                     buttonpress X Y BUTTON_ID |
                     buttonrelease X Y BUTTON_ID
    ;;

where `MENU_ID`, `X`, `Y`, and `BUTTON_ID` are all integers greater than or equal to zero, and `MENU_STR` is a string of characters, possibly including spaces. Let us detail things a bit further:

- Ready event: sent only once, whenever the `adesklets` interpreter becomes ready to process commands.
- BackgroundGrab event: sent every time the background image (image 1) gets regenerated.
- MenuFire event: sent every time the user select an item in a menu not managed internally by adesklets.
- MotionNotify event: sent whenever the pointer moves inside the adesklets window.
- EnterNotify, LeaveNotify: sent whenever the cursor passes over a visible border of the interpreter window, either going in or out.
- ButtonPress, ButtonRelease: sent when a pointer button is clicked inside the interpreter window.

All events but Ready! (namely: MotionNotify, EnterNotify, LeaveNotify, ButtonPress, ButtonRelease, BackgroundGrab and MenuFire) are masquable by the desklet. In fact, `adesklets` provides the desklet with commands for specifically handling the generation and output of events. They are: `event_catch`, `events_get_send_sigusr1`, `events_reset_all`, `event_uncatch`, `events_info`, `events_set_echo`, `events_get_echo`, `events_purge` and `events_set_send_sigusr1`. It should be noted that those functions are not listed in `scripting/prototypes` as you most probably do not want your users having access to them. You should probably start an interactive session and play with them to make sure you fully understand this. This is also the right time to mention the `src/adesklets_debug.sh` script, which comes in handy when interactively experimenting with events.

Two last things are worth mentionning about events:

1. By default, events are not echoed, but stored internally, waiting to be purged with `events_purge`. You can change this with `events_set_echo`.
2. You can set the adesklet interpreter to send a SIGUSR1 signal every time an event is printed out to its parent process (the desklet). It is a very useful IPC, since the signal can be trapped, allowing the event management routine to run only when necessary. This is what the Python package uses within its `Events_handler` class.

### 6.4 SIGCHLD signal handler

Whenever your language allows it, you should install some kind of SIGCHILD handler, so you can at least be notified if the adesklets interpreter ever exists without a reason (child zombies notwithstanding)16.

### 6.5 Textual variables

`adesklets` also supports differed execution (indirect mode) and textual replacement of variables. Here how variables work in indirect mode:

1. Once a command is entered, it is stored untouched into the commands history list, if applicable (in non-interactive usage of the interpreter, when the command lies bethween calls to `start_recording` and `stop_recording`)
2. When a macro command is played back (using the `play` command, See Programming adesklets.), variables get expanded in a single pass, and no indirect reference is possible. Expansion is pretty straightforward. The stored command line is scanned for any non-empty sequence of non-space characters beginning with a single '$'. Each such sequence is replaced with the corresponding `$variable` value, or removed if no variable is found.
3. Expanded command get executed.

The same expansion mechanism also applies to the direct mode of execution. All this leads to the fact that, using the Python package for instance, these two code snippets are equivalent:

         adesklets.window_resize(100,100)

and:

         adesklets.set('dim',100)
         adesklets.window_resize('$dim','$dim')

This did not require any alteration of our Python code, since Python is a dynamic language, but some other languages will require further thinking.

### 6.6 Final words

You should now know pretty much everything needed for integrating adesklets with your language environment. It may appear to be a daunting task, but a very clean binding can be produced by one programmer in only a few days. Once again, have a look at `src/adesklets_debug.sh`; it is a (very lame) Bourne shell binding for adesklets written in only fifty lines, including comments.

If you ever need assistance, you are encouraged to post on the adesklets-devel mailing list ...

Happy coding!

#### Footnotes

[1] We use the term “langage environment” because the problem is not the syntax or the paradigm used (should it be imperative, functional, or anything else); it is the way you can handle basic POSIX-related operations with files, signals, etc. (See Requirements.). Thus, a BASH user should use adesklets easily, while a Linux JDK user would probably encounter more difficulties (no flames intended).

[2] As of adesklets 0.6.1, both Python and Perl are supported out of the box.

[3] This said, having both IPC support and poll/select will make things both simplier and cleaner. Being able to block signals is also very useful.

[4] This would be a wrapper, basically.

[5] With a call from the execve* family; see `man 2 execve`.

[6] They are, namely, its screen and coordinates.

[7] Which means the session is interactive

[8] A '\n' character should be emitted after each command.

[9] This is to avoid process blocking by overflowing the stream with arbitrarily long commands; of course, there is no problem sending three commands of a few hundred bytes in one shot!

[10] See `scripting/protoize.sh.in` and `scripting/enums.sh` for details on the format of the two files.

[11] The key idea being that most users of your binding should be able to run it more or less from their base installation of the language.

[12] Nowadays, there is no problem running GNU sed on practically any POSIX system.

[13] All parameters from the `MESSAGE_STRING` are separated by single spaces.

[14] No event is ever lost anyway; its report is only delayed.

[15] All other outputs on `stderr` can safely be discarded.

[16] `man 2 signal` is a very good reference on signals.