Next: , Previous: Using adesklets, Up: Top


5 Programming adesklets

5.1 An adesklets' primer

As previously stated (See About adesklets.), adesklets is basically an interactive Imlib2 console, with one or two aditionnal features:

So let's start a typical interactive session to see what happens. Under X, open a terminal, and type:

     adesklets :

As in last section (See 'Using adesklets as a command-line graphic editor'.), you will get a prompt. Now, enter the command 'images_info'. You should get on stdout:

     2 images
     id 0 width 1 height 1 alpha 1 filename (null)
     id 1 width 1 height 1 alpha 1 filename (null)
     command 0 ok: images_info

This tells you that you have two images: image 0 and image 1, each 1x1 pixels. Those are the only images you can never destroy nor do any operations you want with (resize them independently, flip them diagonaly, etc.) Why is that?

But where is this window? Well, right know, it is only 1x1 pixels, and it is not shown1 on the screen. So let's resize it to 100x100 pixels: type 'window_resize 100 100'. Now, if you re-enter the command 'images_info', you will get:

     2 images
     id 0 width 100 height 100 alpha 1 filename (null)
     id 1 width 100 height 100 alpha 1 filename (null)
     command 2 ok: images_info

The foreground and background images have been resized to 100x100 pixels. Moreover, the background image has been updated to contain a new image of the background. Now, let's make this window visible. Type two commands: 'window_reset managed' and then 'window_show'.

The first command tell adesklets to let your window manager “manage” your window (the default is not to); it means the window gets decorated, and you can interactively change its visibility2. The second command shows our 100x100 window. So far, it's nothing too impressive–just a black 100x100 square with a titlebar and fixed-size borders.

As mentioned above, only image 0 (the foreground) is displayed by default. Now, type: 'window_set_transparency 1'. Wow! We see the root window below! With “window transparency” on, the image 1 (background) is first copied onto the window, then image 0 (foreground) which is purely transparent black, is blended onto it3.

It is time to say a word about colors. Remember, adesklets is an Imlib2 console. Thus, as with Imlib24, colors are always true 32 colors, RGBA encoded, with eight bits (0 to 255) per channel, including an alpha channel for transparency. Palette conversion and such will automatically take place if your screen depth is less than that, so you do not need to bother with that. If you type: 'context_get_color', you will get:

     command 0 ok: context color 255 255 255 255

Imlib2 is a kind of state machine; as such, it comes with a “context” that remembers a series of attributes it will use in its future operations. For instance, we now know the color it will use as long as we do not modify it is opaque pure white (red=255, blue=255, green=255, alpha=255). If we type: 'context_get_image', it returns:

     command 0 ok: context image 0

Which means all window operations we perform are made directly on the forgeground image. So, let's draw a filled semi-transparent magenta rectangle on the foreground. Commands could be: 'context_set_color 255 0 0 200' and 'image_fill_rectangle 25 25 50 50'. Neat, isn't it?

If you are used to Imlib2 programming, you will have noticed by now those last few commands look quite familiar. This is pretty normal; most of Imlib2 C functions are presented as corresponding 'commands' in adesklets. This way, adesklets' command 'image_fill_rectangle' follows the same semantic as Imlib2's 'imlib_image_fill_rectangle()' function, the command 'context_set_color' functions just like 'imlib_context_set_color()', etc. The only two significant (and systematic) differences are that whenever you would use an 'Imlib_Something' object in C, you use an integer ID with adesklets, and on the semantic of the 'imlib_free_something()' functions that need to be given an ID instead of freeing the context selected object of this type.

This is pretty easy to illustrate. Let's load the “Vera” font included with adesklets at a 20pt size, set it as default font, and write in pure opaque white “Hello” diagonally on the foreground image from the top left corner before unloading the font. It would look like:

     6 >>> load_font Vera/20
     command 6 ok: new font 0
     7 >>> context_set_font 0
     command 7 ok: context_set_font 0
     8 >>> context_set_direction text_to_angle
     command 8 ok: context_set_direction text_to_angle
     9 >>> context_set_angle 45
     command 9 ok: context_set_angle 45
     10 >>> context_set_color 255 255 255 255
     command 10 ok: context_set_color 255 255 255 255
     11 >>> text_draw 0 0 Hello
     command 11 ok: text_draw 0 0 Hello
     12 >>> free_font 0
     command 12 ok: free_font 0

If you look at your imlib2 documentation, you will immediatly notice the two differences we just talked about: all references to the font Vera/20 are made through an integer ID (0 here), and the 'free_font' command, unlike the corresponding 'imlib_free_font()' function, is called with the font ID as an argument instead of the context font.

Now let's say you want to save you resulting image; not a problem! Let us type: 'save_image out.png', and the foreground is saved in the file “out.png” in the current working directory5. Now, just for the sake of it, let us exit adesklets, and start another interactive session to see if we can reload this image. Just type in the “quit” command or press ^D (Control D: end of file). Then your new session should look something like:

     0 >>> window_resize 100 100
     command 0 ok: window_resize 100 100
     1 >>> window_reset managed
     command 1 ok: window_reset managed
     2 >>> window_set_transparency 1
     command 2 ok: window_set_transparency 1
     3 >>> load_image out.png
     command 3 ok: new image 2
     4 >>> blend_image_onto_image 2 1 0 0 100 100 0 0 100 100
     command 4 ok: blend_image_onto_image 2 1 0 0 100 100 0 0 100 100
     5 >>> window_show
     command 5 ok: window_show
     6 >>> free_image 2
     command 6 ok: free_image 2

Of course, we wanted to visualize the result; this is why we emitted commands 'window_reset', 'window_set_transparency' and 'window_show'; they are not otherwise useful. The command 'blend_image_onto_image' comes straight from Imlib2 once again; it takes the newly created image 2 from the third command and blends it onto the context image (image 0, foreground by default), for its coordinates (0,0) to 100x100 pixels farther, and puts it on the rectangle starting at (0,0) of size 100x100 pixels of image 0. It is finally good to note that, with adesklets, there is always an image in Imlib2 context: whenever you try to free the current image, the image context is always reset to the foreground image.

OK, so it is almost the end of this primer... From there you should play around with adesklets. It is pretty straightforward to use if you are already familiar with Imlib2. If you are not, your best resource is certainly having Imlib2 documentation not far away. :-)

Some last tips:

Here is an example:

     #!/usr/bin/env adesklets -f
     
     # Make the window 'managed'
     #
     window_reset managed
     
     # Resize the window to 100x100 pixels
     #
     window_resize 100 100
     
     # Show the window, then freeze for 10 seconds before exiting
     #
     window_show
     pause 10

You will just have to make this script executable and run it. As usual, output from the commands will get printed to stdout.

5.2 Real programming with adesklets: writing desklet scripts

When you are ready for real work7, you might want to use a real scripted language instead of the raw adesklets interpreter we used in the primer; you never know when just having variables could come in handy. At the time of this writing (30 March 2006), Python and Perl 5 support is included in adesklets package 8.

5.2.1 General tips for programmers

A really useful feature of the adesklets interpreter is its ability to produce a full trace of its session (commands, events and special messages), either on stdeerr or as a log file, independently of what is driving it. To have access to this feature, one must:

  1. Configure the package from source, passing along the --enable-debug switch to configure.
  2. Export the ADESKLETS_LOG variable in the environment if the session's output should to be stored in files instead of printed to stderr9. It should be an absolute filename prefix which the interpreter, inheriting from the environment, can use to create complete file names to write to.
From there, it is possible to read complete chronological session logs; used correcly, it is very useful for debugging purposes. You can use it in conjunction with the echo command to set easy to find log points10.

5.2.2 adesklets and Python

Python usage example

From Python, things are not very different than from the interpreter. Commands have been wrapped into Python functions, and an Events_handler class has been made public to handle asynchronous feedback from the interpreter. Let's have a look at the test/test.py script from the source tarball of version 0.6.1:

"""
test.py - S.Fourmanoit <syfou@users.sourceforge.net>, 2004

Small, non-exhaustive adesklets test script:
        - Resize adesklets window to 100x100 pixels
        - Put it under wm's control
        - Set it to be pseudo-transparent
        - Map it on screen
        - Then wait until the user exits,
          generating an alam event every 10 seconds,
          and catching motion_notify events as they occur

To try it:
        - Install adesklets with python support enabled (default)
        - Run python test.py from this directory
"""
import adesklets

class My_Events(adesklets.Events_handler):
    def __init__(self):
        adesklets.Events_handler.__init__(self)
        
    def __del__(self):
        adesklets.Events_handler.__del__(self)
        
    def ready(self):
        adesklets.window_resize(100,100)
        adesklets.window_reset(adesklets.WINDOW_MANAGED)
        adesklets.window_set_transparency(True)
        adesklets.window_show()
        
    def quit(self):
        print 'Quitting...'
        
    def alarm(self):
        print 'Alarm. Next in 10 seconds.'
        return 10
    
    def motion_notify(self, delayed, x, y):
        print 'Motion notify:', x, y, delayed
        
My_Events().pause()
That's it! Twenty-six lines of Python code, and we have a perfectly functionnal desklet. I think things are pretty self-explanatory; have a look at the Python online help for adesklets, adesklets.commands and adesklets.Events_handler for a good, complete explanation (See Python package documentation.) . All we do here is:
  1. Import the adesklets package: this automatically instantiates a child adesklets process and sets up all communications. When the package initialization returns without raising any exception, it means the interpreter is up and ready to accept commands.
  2. Subclass adesklets.Events_handler and redefine the methods invoked for the events we are interested in (all other events are not just ignored, they are not generated).
  3. Instantiate an object of this class, and put it on hold indefinitely 11.
Gotchas: a bit more about object IDs

All adesklets objects (fonts, images, color ranges, polygons, etc), are stored in stacks (one stack per object type). Python's ID's are nothing more than the initial position of given objects inside the relevant stack, and therefore will become invalid if an object of the same type below them gets freed.

For instance, if we start with a clean state, all we have in the stack of images is the window's foreground (ID 0) and background (ID 1). When someone creates two other images from Python like so:

     im_1 = adesklets.create_image(10,10)
     im_2 = adesklets.create_image(10,10)

We get im_1==2, and im_2==3. Whenever someones does:

     adesklets.free_image(im_1)

The stack starts collapsing, and what was once image 3 (im_2) is now image 2, and im_2 is now an invalid reference. This clearly means that invoking:

     adesklets.free_image(im_2)

would generate an image out of range error message.

Special subject: animations

adesklets now includes an indirect mode of execution and textual variables (non-recursive textual replacements, to be precise) as well. This means desklets writers have what is needed to create precisely-timed animations. You can find an easy-to-follow example in test/fading.py. A somewhat more involved use of these features is also made in the yab desklet. To realize an animation from Python, one should:

  1. Record a sequence of commands (we will call it a macro command, or macro for now on). From Python, this involves toggling on indirect mode using the adesklets.start_recording() function, emitting an undetermined number of adesklets commands, then toggling back into normal mode (direct execution) with adesklets.stop_recording(). Of course, no command is ever evaluated while the interpreter is in indirect mode; commands are only stored in the history for future replay.
  2. Set up whether the to-be-replayed sequence of commands can be interrupted by events or not, using the adesklets.play_set_abort_on_events() function. The high-level adesklets.Events_handler::set_events() method can also be used to dynamicaly change what events are caught. See test/test_events.py for an example.
  3. Set up the variables used within the recorded macro using the adesklets.set() function, very similar to its Bourne shell homonym.
  4. Play the macro using the adesklets.play() function.

Here are a few additional remarks:

Configuration and internationalization

Let us just mention here that from adesklets 0.4.0, the adesklets.utils module now includes an optional ConfigFile class that can be easily reused by desklets authors to add an easily extendable configuration facility to their code12 (See Python package documentation.) . The class also handles internationalization automatically by default, setting the charsets according to users' needs. Charset usage can of course be determined or changed at any time using the adesklets.get_charset() and adesklets.set_charset() functions. The adesklets.utils.ConfigFile class also has a charset attribute one can examine to determine the user's preference. When using this class, one should note that an 'ASCII' charset is considered the default, and will not toggle any conversion. This way, users on platforms not supporting iconv will always be able to use adesklets without internationalization support.

Final words

Finally, let us mention you may want to take a look at the weather desklet source code from the SourceForge project web site for a more substantial piece of Python code using adesklets. There are also a few other test desklets under test/ that may give you some ideas. You could also have a look at pydoc's autogenerated adesklets package help, included with this document (See Python package documentation.). Whenever you are happy enough with your desklet, feel free to share it with the rest of us (this would be much appreciated)–package it (See Packaging GNU Makefile for desklets.), and then submit it (See Submitting a desklet.).

5.2.3 adesklets and Perl

From adesklets 0.6.0, adesklets can now be scripted out of the box from Perl, thanks to the work of Lucas Brutschy . Of course, the Perl bindings are completely orthogonal from Python, and only needs a Perl 5 environment in addition to the core adesklets interpreter, written in C.

Perl usage example

As with the Python package, commands were wrapped into Perl subroutines, and an event loop can then be triggered, automatically registering and calling the given callbacks (BackgroundGrab, ButtonRelease, LeaveNotify, MotionNotify, ButtonPress, EnterNotify, MenuFire) as appropriate. adesklets::event_loop only returns when the driven adesklets interpreter dies. Here is the test/test.pl script from the source tarball of version 0.6.1:

# test.pl - Lucas Brutschy <lbrutschy@users.sourceforge.net>, 2006
#
# Small, non-exhaustive adesklets test script:
#        - Resize adesklets window to 100x100 pixels
#        - Set it to be pseudo-transparent
#        - Draw a translucent light grey rectangle on it
#        - Map it on screen
#        - Mark with a red dot the pointer position every time 
#          a user click
#
# To try it:
#        - Install adesklets with perl support enabled (default)
#        - Run perl test.py from this directory

use strict;
use adesklets;

adesklets::open_streams();

# these are just normal adesklet commands
adesklets::window_resize(100,100);
adesklets::window_reset(adesklets::WINDOW_UNMANAGED);
adesklets::window_set_transparency(1);
adesklets::context_set_color(255,255,255,64);
adesklets::image_fill_rectangle(5,5,90,90);
adesklets::window_show();

adesklets::event_loop(ButtonPress=>\&onbutton); # supply a hash of callbacks
adesklets::close_streams();

sub onbutton
{
    my($x,$y) = @_;
    adesklets::context_set_color(255,0,0,255);
    adesklets::image_fill_rectangle($x,$y,3,3);    
}
Merely twenty lines of code, and as previously, we already have a functionnal desklet. It should be pretty straighforward to understand, but here are a few general indications on what it does:
  1. First, it imports the adesklets Perl module (use adesklets), and instanciate the adesklets interpreter calling adesklets::open_stream(). Once the subroutine returns, the command line is already processed and the interpreter is ready to accept commands.
  2. From there, we simply initialized an unmanaged 100x100 window, fill it with a translucent white background, and show it.
  3. Finally, we fire up an event loop, registering a callback subroutine to take action when the user click on it (here, the action is to draw a 3x3 red square under the pointer). This event loop runs as long as the adesklets interpreter is not killed (either manually or because of the end of the X11 session). When it returns, the cleanup subroutine adesklets::close_streams() is called.
Catching errors

When an adesklets command returns an error, the Perl script will die. You can recuperate the original error message like this:

     eval { my $image = adesklets::load_image("aaarrhg"); };
     if($@) { print $@; }

See General tips for programmers, for an alternative.

Configuration

You can very well keep around multiple, differenly configured instances of the same Perl-based desklet; it is simply a matter of using the return value of adesklets::get_id() to uniquely identify the running instance; the way you choose to use this unique integer id to select configuration parameters is entirely up to you.

Final words

You might want to look at previous subsections concerning object IDs (See Gotchas: a bit more about object IDs.) and animations (See Special subject: animations.), that still holds for Perl.

You could also have a look at the plain old documentation (POD) of the Perl package, included with this document (See Perl package documentation.). As mentionned previously, whenever you are happy enough with your desklet, feel free to share it with the rest of us (this would be much appreciated)–package it (See Packaging GNU Makefile for desklets.), and then submit it (See Submitting a desklet.).


Footnotes

[1] “Mapped” in X Window lingo

[2] An “unmanaged” window is very useful for scripted desklets: it looks like it is part of the root window as it can be made to never go on top, and to stay mapped when workspaces are switched.

[3] Well...Not quite. Actually, there is a buffer involved for performance, but the operation is logically equivalent to this description.

[4] Keep your Imlib2 documentation nearby; it is very handy! See Imlib2 documentation.

[5] For this to work, you need to have your Imlib2 installation properly configured for using libpng, or you could receive a “no loader for file format” error.

[6] For this to work, you need to have GNU history when building adesklets.

[7] :-)

[8] adesklets was written to be easily used from a variety of interpreted languages – do not hesitate to propose your help if you want to bring support for your language of choice!

[9] Printing to stderr will break most bindings, and should therefore be used mainly when invoking the interpreter directly from the console.

[10] A few packaging systems, such as the ebuilds in Portage through the debug USE flag, does support this feature automatically.

[11] This last step is not mandatory; the script may very well continue, but it should be written so that it supports signal interruptions. See help(adesklets.Events_handler) for further explanations

[12] You can look at any non-contributed configurable desklet for example usage.