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.
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.
As you probably know (See Using adesklets.), there are two modes of operation for adesklets, as it can be called:
On a new X session, the typical startup sequence is:
ADESKLETS_ID
environment variable with a string representation of the proper
integer ID, then execute 5 the associated script.ADESKLETS_ID
variable without altering
it. It is the desklet's duty to make sure that:
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.stdin
and reads to the two
other streams are all unbuffered.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.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.
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.
As explained previously, a desklet communicates with its private adesklets interpreter via redirected unbuffered streams. Here, let us see what information they convey.
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.
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:
create_image
and such)context_get_color
and the like)images_info
and the like) for commands that
output multiple lines on stdout
, with the last
line being the status messageAlgorithmically, 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.
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:
events_purge
. You can
change this with events_set_echo
.Events_handler
class.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.
adesklets also supports differed execution (indirect mode) and textual replacement of variables. Here how variables work in indirect mode:
start_recording
and
stop_recording
)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.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.
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!
[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 Perl 5 user should use adesklets easily, while a Linux JDK user would probably encounter more difficulties (no flames intended).
[2] As of adesklets 0.4.10, only Python is 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.