Programming adesklets - adesklets 0.6.1
Description: adesklets 0.6.1

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

- When an X display is present, automated management of a single window
- Automated support for pseudo-transparency
- Management of long-running applets

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?

- Image 0 is the foreground image. By default, it is the only image that is displayed on the single window that `adesklets` manages.
- Image 1 is the background image. By default, it always contains the content of the root window (the desktop) directly below the window. Unless we set it to be otherwise, it is always updated when the user moves it, when you resize it, when your wallpaper changes, etc.

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:

- If you get tired of typing, and typing, do not forget you have autocompletion: 'TAB' your problems away!
- If you want to have hints on how to do something, you have a built-in '`help`' command.
- If you are working out a series of commands, things are not working and all this interactive usage it getting tiresome:
  1. Use the '`history`' command to output your current session to a file6.
  2. Use the '-f' switch with adesklets to get commands from a file instead of the console tty.
  3. If you try to visualise something, insert a '`pause`' command at the end of the script to freeze the interpreter.
  4. All strings from a pound sign '#' to the end of a line will get discarded, allowing you to write comments.

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.

- You could also use the `play` command. This two-arguments command gives you a way to replay any sequence of commands from history, using the command number of the first and last command as parameters.

### 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 `stderr`9. 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:

- Precise timing during replay is achieved through the `time_gate` command. This works very simply: every time a macro replays, adesklets records its starting time, and whenever a `time_gate` command is encountered during the replay, it waits for the number of seconds given as an argument to `time_gate` to run out. For instance, specifing `time_gate 0.1` in a macro will make any subsequent commands within the macro not execute before the macro has run for at least a tenth of second.
- Variable expansion always occurs just before command execution, regardless of current interpreter mode (direct or indirect). All sequences of characters not containing spaces directly following a single '$' will get replaced with the corresponding variable content (previously configured using `set`). If no matching variable is found, the sequence is simply discarded.
- All macro commands replayed through the `play` command are always atomic for the driving desklet, as are any other non-macro commands. Simply put, it means no event will ever get generated while a macro is replayed; they will all get issued right after the `play` command returns.
- As usual, the adesklets interpreter is stateful for indirect mode related states and variables. You do not have to set up the `set_abort_on_events` flags or variable every time you want to call a macro, only if something needs to be changed since the last call.
- The main use of high-level `adesklets.Events_handler::set_events()` is to stop the catching of specific events during macro playback, so the macro can be interrupted only in specific circumstances. Of course, all events generated before the call to the method will not be lost but queued, and appropriate event methods will be called later, provided the handlers are set back once the macro playback completes.
- Did we mention that it is a bad idea to do any of this from an unprotected place? Well, it is. Don't forget to use `adesklets.Events_handler::block()` and `adesklets.Events_handler::unblock()` around all animation-related code if you have an object of a child class of `adesklets.Events_handler` instantiated.

##### 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.