Next: , 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:

It should also be able to either3:

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:

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_STRING13. 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:

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.