1. Foreword

Welcome to djnn, the software development framework that will help you produce innovative user interfaces throughout efficient design and development processes.

djnn is an extensible environment, and we continuously develop extensions for supporting richer user interfaces. The current version includes the core system of djnn, the graphical module that implements the core of the SVG model, finite state machines for describing discrete behaviours, data-flow bricks for describing continuous behaviours, and initial support for input management.

The djnn libraries offer two programming interfaces. One is aimed at creating interactive programs in C. The other is aimed at building djnn APIs in new languages by creating a wrapper, and at creating new modules, rendering engines, or servers.

This document is made of four parts. The first two parts are for the general programming public; they introduce the concepts used in djnn and show how to use the libraries to manipulate them in order to build interactive applications. The third part is for more advanced programmers who want to create new djnn modules. The fourth part is for programmers tasked with creating or maintaining djnn wrappers in other programming languages.

The copyright holder on this book is Ecole Nationale de l’Aviation Civile (2007-2014). This book is part of the djnn libraries, and it is made available to you under the same terms as the said software. Please refer to the documents you received when obtaining the software.

2. Introduction

djnn is a model-driven application and user interface framework, designed for prototyping and producing highly interactive and multimodal user interfaces. djnn combines state-of-the-art 2D graphics capabilities with an architecture that supports industrial processes for designing and developing advanced user interfaces. djnn relies on the I* theoretical model of interactive software.

This guide describes the djnn libraries and their C API. In the present chapter, you will find practical information on this guide, and background information on djnn.

2.1. Reading the djnn documentation

2.1.1. How to read this guide

The djnn libraries are aimed at supporting three types of programming activities:

  1. interactive application programming. The djnn C libraries contain the C API for building djnn applications and components, in the same way as there is a Perl API, a Python API, a C++ API and a Java API. If you re in this case, you can read Part I of this guide and skip the rest. You can also refer directly to the (unavailable yet) API reference manual if you are already familiar with the concepts used in djnn.

  2. implementation of a new interaction modality or a new set of services as an djnn module. If you want to interface the new brainwave device that you just acquired, doing it in C will ensure that your module will be usable in all djnn APIs with minimal work. If you are in this case, please read Part I to familiarize yourself with the concepts used in djnn, before reading Part II.

  3. development of an djnn API in a new language. Rather than creating an I* implementation from scratch, you can use the djnn C libraries and encapsulate them in your language of choice. If you are in this case, please read Part I to familiarize yourself with the concepts used in djnn, before reading Part III. You can also have a look at other guides such as The djnn Perl programming guide to get an idea of how the API in a given language can be adapted to the idioms of this language while respecting the principles of djnn.

2.1.2. Where to find other documentation

There are three companion guides to this document:

  • the djnn libraries reference manual (not available yet) contains the description of all public and semi-public functions in the libraries, as well as the XML formats defined by djnn.

  • the djnn libraries cookbook contains sample code for achieving given tasks, from drawing to architecturing your code or extending the djnn main loop.

  • the djnn libraries contributing guide contains useful information for working in the sources of djnn.

These documents are available in PDF and in HTML. On Linux systems, you can know the exact location of the HTML documentation by typing man djnn.

These documents are mainly aimed at programmers. Guides or guide chapters for other actors of the design and development process are planned, but not yet available. They include information for graphic designers on how to produce artwork that programmers can readily reuse, and suggestions for project managers on how to organize the workflow in multidisciplinary groups. Check out later versions of the documentation!

2.2. About djnn

This section provides background information about the djnn environment: its philosophy, its components, etc. Reading it will provide you with interesting context. However, the hurried programmer can skip it with no harm, and maybe come back to it when curiosity grows.

2.2.1. A model-driven architecture

djnn is a model-driven framework. You may wonder: what does it mean and why? Let us start by the why. One of the problems that limits the availability of highly interactive user interfaces is the difficulty for industrial companies to combine their software engineering processes with the iterative processes required by user interfaces. Iterative prototyping can be costly if made with inappropriate tools. And using different tools for prototyping and development can lead to disillusions when it appears that they do not have the same capabilities and that the original design must be thrown away. Model-driven development is a solution to these problems, among others.

With model-driven development, you rely on models that researchers have produced for your domain of interest, and you create and assemble instances of those models. Instead of writing lines of code, you create data. It is not that much different, except that:

  • your model instances can be stored as data files, in XML format for instance;

  • the libraries or programs that run your models can be checked for compliance with the models.

For instance, the graphical model used in djnn is compatible with the SVG format. This guarantees compatibility between the graphical tools used by graphical designers and for early prototypes, the Perl or Python execution environment for prototypes, and the C or C++ execution environment.

2.2.2. From prototyping to development

djnn implements a model-driven architecture for user interaction. It builds up on that approach to propose new processes for building user interfaces, where the reuse of code from prototyping to development is maximised while respecting the nature of each phase of the cycle.

Prototyping is a phase where iterations and flexibility are central. Participatory design methods even recommend the use of paper prototypes to achieve those qualities. When an interactive prototype must be produced, interpreted languages are usually more appreciated than more software engineering oriented languages. Then when reaching specification and development time, the original code is usually thrown away.

However, there is no reason to throw away the images that were produced during prototyping: no extra quality is to be obtained by "recoding" them. Taking advantage of its model-driven architecture, djnn extends that reusability to other parts of the user interface: the graphical objects, the interactive behaviours, the component structure, and more to come. This is possible because with djnn you do not code these aspects: you instantiate the corresponding models. Once a model is produced, it can be reused. This feature dramatically reduces the time and effort from the original design to the final implementation. In many cases, programmers do not even have to write one single line of graphical code: all the graphics were produced during the design phase and are reused during the development.

2.2.3. Ready for graphical editing

Another feature of model-driven architectures is the ability to build or reuse graphical editors for instantiating and assembling models. The only constraint is to build models that conform to the standard format. Such editors are already available for the graphical model used in djnn, starting with the industry standard Adobe Illustrator. Specific editors can be developed for other models used in djnn.

2.2.4. Multimodal-ready!

Support for multimodality requires two things: a framework core that accomodates the use of multiple interaction sources at the same time, and support for new modalities. The djnn core provides the former. And additional djnn modules provide the latter, starting with animation and speech grammars.

2.2.5. Not a widget library

Many toolkits for graphical user interfaces are actually a set of widgets that you assemble for building point-and-click (or sometimes more advanced) user interfaces. djnn is not one of those toolkits, for several reasons:

  • djnn is aimed at modern user interfaces, that go beyond the scope of classical widgets. djnn is made for you to design new widgets, not only assemble existing ones;

  • djnn is an architecture framework for programming interactive software, not only a set of interaction components;

  • djnn does not give a privileged role to any interaction modality; it provides a set of primitives for building interactive components, and you can any modality in these components.

  • one of the core capacities of djnn is the ability to redefine the appearance or behavior of components , which challenges the classical architecture models of widget libraries;

  • several component libraries for different interaction styles are in preparation and will be available as djnn modules.

3. Setting up the djnn libraries

3.1. Introduction

Before going into further details about how to program with the djnn libraries, we need to settle a few practical issues. This is the goal of this chapter.

The chapter starts with practical instructions for installing the libraries from binary packages or making them from the sources. It then provides more details about the structure of the libraries, before explaining how to compile your own code against the libraries.

In the practical instructions that follow, we often refer to djnn modules. This concept is described in the section about the API structure. For the time being, you just need to know that djnn comes as a set of libraries that each implement an djnn module.

3.2. Installing binary packages

The djnn development team proposes binary packages for some operating systems. Here are instructions on how to use them.

3.2.1. Mac OS X

If you have chosen to save the packages to disk when downloading them, or if you have obtained them on disk, you can double click on every package and follow the instructions to let the packaging system install the libraries and headers. If you click on the links of the djnn download page, the packaging system should launch automatically and there is no need for double clicking.

Install all needed packages. There is one for each module and the only one that is required in all cases is the djnn core. Each package installs the corresponding header files in /usr/include and libraries in /usr/lib so that they are easily accessible.

3.2.2. Red Hat-like Linux

If you have chosen to save the RPM packages to disk when downloading them, or if you have obtained them on disk, you can double click on every package and follow the instructions to let the packaging system install the libraries and headers. If you click on the links of the djnn download page, the RPM packaging system should launch automatically and there is no need for double clicking.

Install all needed packages. There is one for each module and the only one that is required in all cases is the djnn core. Each package installs the corresponding header files in /usr/include and libraries in /usr/lib so that they are easily accessible.

3.2.3. Debian-like Linux

If you have chosen to save the DEB packages to disk when downloading them, or if you have obtained them on disk, you can double click on every package and follow the instructions to let the DEB packaging system install the libraries and headers. If you click on the links of the djnn download page, the packaging system should launch automatically and there is no need for double clicking.

Install all needed packages. There is one for each module and the only one that is required in all cases is the djnn core. Each package installs the corresponding header files in /usr/include and libraries in /usr/lib so that they are easily accessible.

3.2.4. Windows

You should have obtained the djnn libraries as a zip package named djnn-all-xxxx.zip. Unzip this archive, and copy the DLL files where needed.

The djnn libraries have dependencies on other libraries; all the required DLLs are available in djnn-Thirdparty-dll.zip, which can be obtained at the same place as the djnn libraries themselves. You need to either install these DLLs to your system or to add the path to them to your Windows variable $Path.

3.3. Building from the sources

If you have obtained a source version of the djnn libraries, this section will help you to build a binary version. If you have installed djnn as one or more binary packages, you can skip this section.

In principle, building the djnn libraries should be as simple as:

% cd libs
% make

for a Unix-like machine and

> cd libs
> nmake -f Makefile.vc

on a Windows machine.

But you might have to install a few tools before that, or you might want to compile only a particular library, or to pass compilation options. This section gives indications that should help you.

3.3.1. Prerequisites

The djnn libraries are C libraries. To compile them from the sources, there are a few requirements:

  • you need make (on Linux and MacOS X) or nmake (on Windows machines). On Windows machines, nmake is available in the free, downloadable, non-graphical Visual C++ Express environment.

  • you need a C compiler. On Linux and MacOS X machines, the default set in the Makefiles is gcc. On Windows machines, the default is Visual C. You can use the one from Visual C++ Express.

  • the XML sub-module of the core module requires the gperf tool and the expat library with its header files (on Linux, you will need libexpat-devel or a similar package).

  • if you want to build the version of the GUI module that uses Qt for rendering, you need a C++ compiler. On Linux and MacOS X machines, the default set in the Makefiles is g++.

On Windows machine, you can find more detailed instructions for installing prerequisites in the libs\_mswin32\README file.

3.3.2. Directory structure

The libs directory contains all the C djnn libraries, that is all libraries usable in C. Some of these libraries might contain C++ code if they use external C++ libraries to implement rendering engines, but they are usable in C nevertheless.

The sources of each library reside in a subdirectory of the libs directory. The only exception is the core module, which has a subdirectory for each sub-module: utils, tree, execution, syshook, xml.

Every subdirectory has the same structure:

  • a generic directory that contains all source files that are common to all platforms.

  • platform-dependent directories, such as mswin32, unix or linux. These directories contain both the adequate Makefile for a given group of machines, and source files that contain platform-specific code. The level of granularity of these directories can vary: sometimes there is a linux and darwin, sometimes only unix, sometimes unix and darwin. The latter case means that both variants work on a Mac.

  • packaging directories, such as rpm, win, or osx that contain files necessary for building binary packages containing the libraries.

To compile the libraries, you should go to the appropriate sub-directory (example: base/unix) and type make or nmake depending on your OS.

To make and run tests, you should type make test from the same directory. The sources of most test are located in the generic/test subdirectory but they are built from the test subdirectory of the platform-dependent directory.

If you wish to run tests by hand, you should do something like:

setenv LD_LIBRARY_PATH your_working_directory/local-install/lib
export LD_LIBRARY_PATH=your_working_directory/local-install/lib

on Linux or

setenv DYLD_LIBRARY_PATH your_working_directory/local-install/lib
export DYLD_LIBRARY_PATH=your_working_directory/local-install/lib

on Mac OS X before running them.

3.3.3. Compilation configuration and options

Platform-specific parameters common to all sources are centralised in files _unix/arch.mk (for Linux and MacOS X) and _mswin32/arch.vc. However, you can add compilation options when launching the compilation, through the DBG variable which is meant for passing debug options but can be used for other purposes. make DBG=-g is the canonic example.

3.4. API structure

3.4.1. djnn modules

The djnn environment is organised around the concept of module. An djnn application uses the core module, plus optional modules depending on the desired interaction modalities. There are:

  • a core module that contains the core of djnn: all that is necessary to create, manage and run components. You can see the core module as the core of a programming language.

  • a base module that is to the core module what the base library is to a proggramming language. The base module currently defines basic event sources (clocks, Ivy bus) and control structures to create discrete interactive behaviours.

  • a GUI module that contains the basic components for creating graphical user interfaces: graphical components based on the SVG model, windows, and the classical associated sources: a mouse and a keyboard.

  • a MacBook module that contains components that are specific to MacBooks: accelerometer, ambient lighting sensor, and keyboard lighting.

In addition to these, the following modules are currently under development:

  • a files module that allows to track the creation or modification of files in the file system.

  • a sound module to produce sound.

  • an input module, with a mechanism for detection and classification of input devices.

  • a Wifi module, with components that report on the availability of Wifi networks.

  • a network module, with components that inform on the availability and status of network interfaces and routes.

These modules come as separate libraries, so that you can pick only the necessary modules when deploying of your applications. Only the core module is mandatory for all applications. You may decide to develop an application that does not use the GUI module, for instance.

You may also decide to develop a new module to handle a new input or output interaction modality, although you will probably prefer to check our plans for module releases first. New module can be a library, a package or a plugin; what is important is that programmers can choose to load them or not.

3.4.2. One library, two headers per module

The full name of each library depends on the operating system and compilation options. However, all have the same radix djnn-modulename. As for headers, they are all stored in a directory named djnn, and are named either modulename.h or modulename-dev.h. The former is a public header, aimed at using or encapsulating the component-creation functions of the corresponding library. The latter is a semi-public header, aimed at people who want to create a new module with components derived from those offered by the library.

Taking the example of the Base module on MacOS X, this gives:

  • libdjnn-base1.1.dylib, dynamic library for version 1.1 of the base module.

  • djnn/base.h, the public header for the base module; it contains all declarations for end programmers who want to create clocks, finite state machines, Ivy access components or other base components in their applications.

  • djnn/base-dev.h, the semi-public header for the base module; it contains additional declarations for programmers who want to create specialisations of components from the base library. This should not happen very often.

3.4.3. Programming conventions

The functions in the djnn libraries belong to one of the three following sets:

  • those starting with djn, such as djnCreateComponent, are called public functions. They are the ones required to create djnn applications by assembling existing components. You will only meet these in Parts I and II of this guide.

  • those starting with djn_, such as djn_AddSymbol and djn_InitSystemHookHere, are called semi-public functions. They are useful for either creating new modules in C or create a wrapper in another language. You will meet them in Parts III and IV of this guide.

  • those starting with djn__ are private functions. Only the maintainers of the djnn libraries should use these.

3.5. Compiling your own djnn code

3.5.1. Compiling an application

Let us use a simple example, which assembles three djnn components in a larger component in order to emit a beep every second:

#include <djnn/core.h>
#include <djnn/base.h>
#include <djnn/sound.h>

main (int argc, const char** argv)
  djnComponent *c, *cl, *be, *bi;

  djnInitCore ();
  djnInitBase ();
  djnInitSound ();

  c = djnCreateComponent (0, 0);
  cl = djnCreateClock (c, "clock", 1000);
  be = djnCreateBeep (c, "beep", 1);
  bi = djnCreateBinding (c, "binding", cl, 0, be, 0);

  djnRunComponent (c);
  djnRunComponent (djnSystemHook);

  return 0;

This example uses components from the Sound and Base modules, and of course it uses the djnn core. Therefore, the instruction to compile it looks like the following:

cc example.c -ldjnn-sound -ldjnn-base -ldjnn-core

Notice the use of djnn header files and djnn libraries. Had application used more modules, such as the GUI module, more headers and more libraries would have been required.

3.5.2. Compiling your own djnn library or module

If you create your own module or suporting library, for implementing a new modality or just for factorising your own set of components so as to reuse them in several applications, the simplest thing you can do is to copy and adapt the structure and Makefile of an existing module. See for instance the contents of the dummy-module example in the djnn libraries cookbook.

3.5.3. Wrapping the djnn modules in another language

To be written. But mostly, compilation issues depend on your language.

Building interactive applications

1. The application tree

This chapter describes how to build an interactive application by creating, running and evolving an djnn tree. It introduces the concept of component and a few basic component types, shows how to build the application tree with components and how to refer to components in the tree. It ends with a simple description of the execution of an interactive application.

1.1. Introduction to the djnn tree

In chapter Setting up djnn you have seen how to compile and run a simple djnn application. We will now see how to build more complex applications.

1.1.1. A tree of components

The central structure of an djnn application is a tree of components. Components are the nodes of the tree. If you know XML, the concept will be familiar to you: in the same way that an XML tree is made of XML elements, an djnn tree is made of djnn components. Actually, we will see later that djnn components can be created from XML elements, and stored as XML elements.

All user interaction modules provided by djnn, starting with the GUI module, come as a set of djnn components that you can use to build your application tree. For displaying a rectangle in a window, you will use a Rectangle component and a Frame component, for instance. For emitting sounds you can use a Beep component. For managing touch input you will need to deal with Pointer components. And for defining the behavior of your application you will use control components such as Binding, Connector or FSM components.

1.1.2. Composite components

Some components can contain children, and this is how you build your application tree. "Component" has the same meaning here as in the expression "component programming". Composite components are there to help you structure your application, provide reusability, and ensure encapsulation. You build an interactive component (some say an interactor) by assembling components into a composite component. The resulting interactor is a component that you or others can reuse to build more complex interactors, software components, or applications. In the end, your application itself is a component. It can take part in larger scale components made of multiple program, or you can reuse it later as a sub-component of a larger application.

Whereas other djnn components are mostly in charge of implementing the basic blocks of user interfaces, djnn composite components are mostly in charge of software engineering issues. Components implement encapsulation and parameterisation through a namespace system. All names exported by children are only visible to their parent and siblings. These names can in turn be exported (with an optional renaming) by the parent component to its own siblings and parents.

1.1.3. Tree traversals

Finally, a word about execution. For the time being, let us consider that the execution of the application is a series of traversals of the tree. During these traversals, all active djnn components are triggered. Being triggered causes the components to implement what they are made for: displaying a rectangle, managing a window, emitting a sound, etc. There are several types of traversals of the tree during its lifetime. Initialisation, rendering and control flows triggered by events are example of such traversals. If you are familiar with 3D graphics, you might like to understand the djnn tree as an extended scene graph. If you are more interested in the theory of languages and compilation, you might prefer to interpret it as the abstract tree of a language, because it is oriented towards structuring software as much as rendering graphics.

Rendering as a traversal of the tree

In the rest of this chapter, we will see in more detail how you can build an djnn tree for your application, and what happens when you run the application.

1.2. Building the tree

1.2.1. Creating components

The most simple way of building an application tree is to create new djnn components by assembling existing components. The following example creates a label component, made of two rectangles and a text. It uses functions whose name starts with djnCreate, that each create a fresh component.

djnComponent *b, *r1, *r2, *l, *t;

l = djnCreateComponent (0, "label");
t = djnCreateGUIText (l, "text", 30, 20, "hello");

b = djnCreateComponent (0, 0);
r1 = djnCreateGUIRectangle (b, "rect1", 0, 0, 100, 50);
r2 = djnCreateGUIRectangle (b, "rect2", 2, 2, 96, 46);
djnAddChild (l, b, "border");


This example shows two ways of assembling components. You can specify the parent of a component when creating it, using the first argument of the djnCreate* function. This is what is done to populate the border component. Or you can create a component with no parent and later assign it to a parent. This is how the border component is added to the label component.

The first two arguments of the djnCreate* functions are always the same: a pointer to the parent, and the name under which the new component will be known by its parent. The djnAddChild has a third argument between these two: the child that you want added.

Giving an identifier is not mandatory, you can pass 0 instead of a text string. However, you will need a name if you want to address your sub-component later, for instance for setting its properties from a resource file or for binding actions to events occuring in that component.

Giving a parent is not mandatory either, as examplified with the border in the sample code above. This can be useful when building complex components. Be careful to keep a pointer to your elements, otherwise you will not be able to access them anymore! An exception to this is when you create a component with no parent but nevertheless give it a name. In that case, the new component will be accessible as a "root" component through the djnn naming system; that is the case of the label in our example.

1.2.2. Loading components

We have seen how djnn components can be created with djnCreate* functions. Here is another way of creating components: loading them from XML data. Each builtin type of djnn components has an XML equivalent, and each djnn module comes with an XML parser that can read the corresponding XML elements. Therefore, you can load individual components from XML files (or from the Web) and use them to build the djnn tree. Or you can load more complex components that constitute a ready-made sub-tree. The following example loads a given graphical component from a SVG file, a whole component from another XML file, and uses them to build a tree.

app = djnCreateComponent (0, "Loaded components");
e1 = djnLoadComponent (app, 0, "test.svg#red-circle");
e2 = djnLoadComponent (app, 0, "component.xml");

djnRunComponent (app);

1.2.3. Cloning components

To be written

1.2.4. Models

In your programs you might wish to create or load components in the sole purpose of cloning them in the future or running them upon given events, which means you do not want them to be run like the others. You can do this by putting them in components that will never be run. This is done by creating a new tree root (a component with no parent). Or, in some cases, you can pass an extra argument named model to the djnCreate* function; this means that the created component is to be handled as a model and should not be run when its parent is run. See for instance the last argument to djnCreateBeep below, which means that beep is a model and should not be run when c is run (note for experts programmers: this is similar to the defun declaration in Lisp or the sub declaration in Perl).

djnComponent *c = djnCreateComponent (0, 0);
djnComponent *b = djnCreateBeep (c, "beep", 1);
djnRunComponent (c); /* no beep */

Many non-persistent components (that is, components whose rendering spontaneously finishes just after starting) are similar to functions in functional or imperative languages and we will see examples where they are useful as models, not because we want to clone them but because we want to activate them explicitly and not when their parent is activated.

1.3. References in the tree

When building the tree, or later when implementing the application behaviour, it is often necessary to refer to existing components. djnn provides several ways of doing that.

1.3.1. C pointers

When programming in C, the most efficient and natural-looking way of referring to a component is to use a pointer to it. That is what we have done in all examples so far, like with b and r in the following code:

djnComponent *b, *r;
b = djnCreateComponent (0, "border");
r = djnCreateGUIRectangle (b, "rectangle");

As you see, all such pointers have type djnComponent*, which is the most basic type of a node in the application tree. We will use such pointers at many places in this manual. Please be aware though that it this not a portable way of referring to djnn components: it does not transpose to ccmponents stored in XML files, for instance. For this, you need to use path references or alternative referencing systems.

1.3.2. Path references

When building the tree, or later when implementing the application behaviour, it is often necessary to refer to existing components. djnn provides several ways of doing that.

c = djnCreateComponent (0, "root");
djnCreateGUIRectangle (c, "r", ...);
djnCreateBeep (c, "b", 1);
p = djnFindElement (c, "r/press");
b = djnFindElement (c, "b");
djnCreateBinding (c, "binding", p, 0, b, 0);

1.3.3. URIs

djnn supports the creation of your own naming systems, including URIs. This is especially useful for referring to components situated in other programs, possibly on other computers:

c = djnCreateComponent (0, "root");
b = djnCreateBeep (c, "b", 1);
p = djnFindElement (0, "ivy://remote/r/press");
djnCreateBinding (c, "binding", p, 0, b, 0);

1.3.4. Extended path references

djnn provides supports for the Xpointer standard defined by the Web consortium. To be completed.

1.4. Modifying the tree

You may need move or destroy components in the tree. To be written.

1.5. Executing your application

1.5.1. Execution phases

Let us come back to the simple complete application from chapter Setting up djnn. The execution of the main procedure can be decomposed in four phases:

#include <djnn/core.h>
#include <djnn/base.h>
#include <djnn/sound.h>

main (int argc, const char** argv)
  djnComponent *c, *cl, *be, *bi;

  /* phase 0: initializing djnn */
  djnInitCore ();
  djnInitBase ();
  djnInitSound ();

  /* phase 1: creating an application tree */
  c = djnCreateComponent (0, 0);
  cl = djnCreateClock (c, "clock", 1000);
  be = djnCreateBeep (c, "beep", 1);
  bi = djnCreateBinding (c, "binding", cl, 0, be, 0);

  /* phase 2: initial rendering of the tree */
  djnRunComponent (c);

  /* phase 3: waiting for interactions */
  djnRunComponent (djnSystemHook);

  return 0;

The initialization phase is mandatory but it is not really part of your application; this phase is even automated in other djnn APIs. This leaves us with three phases:

  • the construction of the tree;

  • the initial rendering of the tree or a portion of the tree, by a call to the djnRunComponent function;

  • the runtime phase, in which the application tree waits for interactions. During this phases, the tree changes in reaction to end-user’s actions, or to any other external events. These changes are then interpreted into visible modifications (or any other perceptible change) of the UI through new renderings of the UI tree.

These phases are described in more details in in the following sections.

1.5.2. Initializing the tree

to be written

1.5.3. Initial rendering

The djnn tree is just a representation of your application and its user interface. Building it ensures that its components are ready to be activated, but it has no real effect. To really start interacting with the end-user, the tree must first be interpreted into perceptible effects: pixels on the screens, sounds, etc. This phase is called the initial rendering of the tree.

Rendering and execution

What does "rendering" mean? In djnn, rendering means executing. The term comes from computer graphics, and djnn extends it to all kinds of components. In computer graphics, images are built in two phases: first, a digital model of objects is created in memory, then the model is "rendered" as pixels on the screen.

Rendering is the operation by which a model is interpreted into its intended effect: displaying a shape on the screen, producing a sound, or just modifying data in the application. Being rendered (or interpreted) is the main role of components when they are executed, and every type of components encapsulates a given type of rendering. Chapters Building native components and Building modules explain how new component types can be created to create new effects.

Formally speaking, the execution of a tree is a sequence of activations of the components in the tree. When activated, composite components execute their children and atomic components trigger whatever rendering they are made for. Once your djnn tree is ready, you need to activate the part (or parts) of the tree that you want to be rendered when you launch your program. Usually you will run the root component of your application, which in turn will activate all its children, but you can decide to run only particular parts of it.

Rendering, or executing, a component is obtained by calling function djnRunComponent. When this is applied to a container component, all its sub-components are executed in depth-first, left to right order; this means that the execution of the second child does not start before the first child and all its children have started executing. This ensures the tree traversal shown in the Introduction chapter.

You can control the initial rendering of the tree by choosing which components to execute. In a simple program, you will create a root component, fill it with children, then start it. But you can be more selective. For instance in the following code, component c2 and its children are not activated, which means that only one window will appear on the screen.

c = djnCreateComponent (0, 0);
c1 = djnCreateComponent (c, 0);
djnCreateGUIFrame (c1, 0, ...);
c2 = djnCreateComponent (c, 0);
djnCreateGUIFrame (c2, 0, ...);
djnRunComponent (c1);

In order to have both windows appear, one should run c2. This is done by either calling djnRunComponent (c) instead of djnRunComponent (c1), or calling djnRunComponent (c2) later during the execution of the application.

Running and stopping components

to be written.

Component order

The order in which components are executed or rendered is often important. Let us imagine, for instance, a hypothetical Morse module and the following code:

c = djnCreateComponent (0, 0);
c1 = djnCreateComponent (c, 0)
c2 = djnCreateComponent (c, 0);
djnCreateMorseLetter (c2, 0, "M");
djnCreateMorseLetter (c2, 0, "A");
djnCreateMorseLetter (c2, 0, "Y");
djnCreateMorseLetter (c1, 0, "S");
djnCreateMorseLetter (c1, 0, "O");
djnCreateMorseLetter (c1, 0, "S");
djnCreateMorseLetter (c2, 0, "D");
djnCreateMorseLetter (c2, 0, "A");
djnCreateMorseLetter (c2, 0, "Y");
djnRunComponent (c);

This example would emit the string ... --- ... -- .- -.-- -.. .- -.--, that is SOS MAYDAY. This illustrates how rendering works: the structure of the tree influences the rendering order. The tree is traversed in depth-first, left-to-right order. A component inserted before (at the left of) another component will be rendered before that other component.

Rendering context

Now let us come back to graphical user interfaces and examine how the rendering of a component can influence the rendering of other components in the tree. Consider the following example with two windows and two texts.

c = djnCreateComponent (0, 0);
djnCreateGUIFrame (c, "f1", ...);
djnCreateGUIText (c, "t1", 50, 50, "Window 1");
djnCreateGUIFrame (c, "f2", ...);
djnCreateGUIText (c, "t2", 50, 50, "Window 2");
djnRunComponent (c);

The four components in c are rendered in the order f1, t1, f2, t2, and the rendering of Frames influences the rendering of texts: text t1 is rendered in the first window, and text t2 in the second window.

This shows that the rendering of a component can affect the rendering of the components that are rendered after it. Actually, this is only true within the same component: components can influence the rendering of components that appear at their right in the same component, as well as their children.

1.5.4. Waiting for interactions

Although djnn makes it possible to write pure computation programs and sequential dialogues controlled by the computer through the command line, most applications you will write will wait for user input or external events during an indeterminate amount of time. The "system relay loop" component makes this possible in two different ways:

  • when running, it engages your program into a waiting loop or equivalent, until you decide to stop it,

  • it routes events traditionnally detected by the operating system: clock events, network events, and input events when appropriate.

After the initial rendering, interactive applications wait for user events, clock events, network events or any other events that will trigger their response, usually in a perceptible way. This is called the runtime phase. During this phase, the control of the application resides with the operating system (or with a main loop, depending what operating system you are using) until the application decides to quit in response to an event.

See section System relay in chapter Execution model for more details.

2. Defining basic behaviors

to be written.

3. Designing reusable components

This chapter explains how to build reusable components.

3.1. Introduction

To be written.

3.2. Reusing a component

(to be written)

3.3. Adding properties

(to be written)

3.4. Making a data-flow brick

(to be written)

3.5. Emitting events

(to be written)

3.6. Symbol management

(to be written)

3.7. Encapsulating contents

(to be written)

4. Behaviour bricks

This chapter describes a collection of components that will help you give sophisticated behaviors to your own components. Some correspond to behavior and execution control patterns, in addition to the state and data-flow management components that we have already encountered. This includes clocks, sets, and lists. Some concern the manipulation of numbers or logical truth values: numeric and logical operations, conditions. Other provide inter-process communication.

4.1. Introduction

to be written.

4.2. Clocks

to be written.

4.3. Finite state machines

We have already mentioned finite state machines as a simple way to manage states in interactive components. We now come back to them with more details and more possible applications.

4.3.1. FSMs, data flow style

When we introduced state machines earlier, we focused on the current state of the state machine and used it to drive the state of another component. We used a connector for this. This is the data flow style for state machines.

This style can be used with other components than the switch components we used earlier. For instance, (TO BE COMPLETED)

4.3.2. FSMs, event styles

State machines can also be used to activate actions, using bindings or similar control structures. For instance, a binding to a beep can be attached to:

  • the state property , so that every state change produces a beep

  • a given state, so that entering this state produces a beep

  • a transition, so that going through this transition produces a beep

Of course, this works with all possible actions, that is with any kind of components used as an action in a binding.

The first option is not much different from the data flow style. The second option corresponds to what computer scientists call Moore machines: state machines whose output are emitted when entering states. The third option option corresponds to Mealy machines: state machines whose output are emitted when going through transitions.

4.3.3. Combining styles

None of the above programming style is mandatory or exclusive from the others: they can be combined at will, even in intricate ways. Consider the following examples:

  • a button in which the state of a FSM are connected to that of a switch that drives the graphical configuration of the button, and all transitions are mapped onto sounds.

  • a transition whose event source is its origin state, and therefore is is immediately gone through whenever the state is entered.

In order to use such combinations, it is useful to understand the order in which the various actions are triggered when going through a transition from one state to another state:

  1. the transitions leaving the origin state are deactivated

  2. the actions bound to the transition are triggered

  3. the transitions leaving the destination state are activated

  4. the state property of the FSM is changed

(Note, though, that the current implementation of djnn has limitations and there are some combination patterns that it does not smoothly accomodate).

4.3.4. Exporting states and transitions

As explained earlier, state machines can be used to represent the state of an interaction. For instance, a graphical button changes state upon actions with a pointing device, and this can be represented with a state machine. Sometimes, this internal state is not only meaningful to define the behaviour of a component, it is also meaningful outside of it, because it is a significant part of the conceptual model of the component. For instance, you might wish to export the state of the state machine from a button, so as to allow the use of the button in the data flow style. Similarly some transitions may play the role, for instance to use a button in event style.

State and transitions from FSMs can be exported as public children of their parent components. For this (TO BE COMPLETED).

4.3.5. Combining state machines

Sometimes there are more than one set of states that are meaningful in a component. Consider for instance a menu item that can react to a pointer and that can be disabled and greyed out. In some cases it is easier to implement it with two state machines: one for the enabled/disabled status, and the other for interaction states.

Various combinations of state machines are possible:

  • independent parallel behaviors, where the two state machines have no connection;

  • communicating state machines, where some transitions of a state machine are bound to transitions or states of the other.

  • hierarchical behaviors, such as the behavior of the menu item mentioned above: the state of the enabled/disabled FSM can be connected to a switch with two branches, one that greys out the menu item, and the other that contains the interaction FSM.

4.4. Switches

4.5. Sets

to be written.

4.6. Lists

to be written.

4.7. Conditions and truth values

to be written.

4.8. Manipulating numbers

to be written.

4.9. Ivy bus access

to be written.

Interacting with the user and the environment

1. Drawing

An essential part of djnn is its graphical user interface (GUI) module. The GUI module is a combination of graphics, drawing surface management, and input management. We focus here on graphics and a small subset of surface management. In this chapter you will learn the basics of drawing with djnn: basic graphical components, object grouping, transformations, adding color and gradients, text capabilities, clipping. This chapter also explains how to load SVG files to populate the djnn tree.

1.1. Getting started

The example below is one of the simplest applications with a GUI you can write. It does not do much, but the code demonstrates the basic code of every djnn program that involves a GUI:

  • load the appropriate modules

  • instantiate a frame

  • execute the application tree

  • wait for interaction

#include <djnn/core.h>
#include <djnn/gui.h>

main ()
  djnComponent *f;
  djnInitCore ();
  djnInitGUI ();
  f = djnCreateGUIFrame (0, 0, "my window", 0, 0, 200, 100);
  djnRunComponent (f);
  djnRunComponent (djnMainLoop);

The first two lines load the public declarations for djnn and its GUI module, and the seventh and eigth lines initialize them. The ninth line creates a Frame, that is a drawing surface that will appear on the display. Every program with a GUI must have at least one frame. The tenth line runs the frame, thus making it appear. The eleventh line ensures that the program does not leave and the frame stays on the display.

1.2. Windowing

The GUI module offers basic support for windowing: creating rectangular windows and sub-windows, managing the cursor. In a later chapter, a more complex model for windowing and display surfaces will be proposed. However, the current model should prove enough for most graphical applications.

1.2.1. Frames

Frames are djnn components that each represent a window on the computer screen. The window is managed by the operating system.

A frame defines an initial coordinate system. The (0, 0) coordinates represent the upper-left corner of the frame. The numeric value ot the x-abscissa increases from left to right, and the numeric value of the y-ordinate increases from top to bottom. This initial coordinate system is used to place graphical objects.

A frame applies to (contains) all the graphical components that are located after it in the same parent component. When two or more Frames are in the same parent, the first Frame applies to all components until the second Frame, and so on. For instance, to create two rectangles in two windows, write:

djnComponent *c;
djnInitCore ();
djnInitGUI ();
c = djnCreateComponent (0, "my component")
djnCreateGUIFrame (c, "frame1", 0, 0, ...);
djnCreateGUIRectangle (c, "rect1", 10, 10, ...);
djnCreateGUIFrame (c, "frame2", 100, 0, ...);
djnCreateGUIRectangle (c, "rect2", 10, 10, ...);

You may find it surprising that a Frame is not a container in djnn. This is a deliberate choice: being aimed at multimodal user interfaces, djnn does not give a privileged place to graphics and windowing. The privileged place is occupied by Components. To build an object made of a window containing graphical objects, create a Component, put a Frame in it, then put graphical components.

1.2.2. Creating sub-windows

(to be written)

Note that an alternative to Frames for creating sub-windows is the use of Clips, described later in this chapter. With clips you can to obtain similar visual results, and more.

1.2.3. Cursors

Cursors are djnn components that allow you to control the mouse cursor. Like a graphical component, a cursor applies to the current Frame. When several cursors are specified for the same frame, the last one will apply.

Note: Cursors have only been tested on Windows.

1.3. Graphical shapes

Graphical shapes are provided as components that you add to the tree. The set of graphical components is similar to those from the SVG standard.

1.3.1. Basic shapes

djnn contains the following set of basic shapes: circles (Circle), ellipses (Ellipse), straight lines (Line), rectangles with optional round corners (Rectangle).

You can add a basic shape to your program by calling the constructor of this shape. djnCreateGUIRectangle () will create a new rectangle, and djnCreateGUICircle () will create a new circle. The djnn libraries Reference Manual gives all the arguments you can give to specify the geometry of those graphical components, in addition to the ususal arguments for Components.

Shapes only describe the geometry of graphical objects. When used alone, they are displayed with default visual attributes.

1.3.2. Curves

Besides basic shapes, djnn can also draw arbitrary shapes with both straight line and cubic Bézier curves, with components Polygon and Path.

A polygon is a series of line segments. A path is a series of segments that can be straight lines, cubic Bézier curves or quadratic Bézier curves. A cubic Bézier curve is a curved segment that is defined with two end points and two control points. Each control point determines the shape of the curve by controlling one of the end point tangent vector.

Example of cubic Bézier curves

Mathematically speaking, the basic shapes from the previous section are equivalent to a curve that would construct the same shape.

1.3.3. Images

You can display a pixmap image with an Image component. djnn supports bitmap, gif, jpeg and png files.

If the image file supports transparency (not alpha-blending, only full transparency), djnn will render its transparent areas.

You can apply the full set of transformations to an image. However, the result may sometimes be hard to read.

1.3.4. Texts

For displaying a text, you need to use an Text component.

Upcoming C code


1.4. Graphical style

You can control the appearance of graphical components by specifying how they should be rendered. This is done by using graphical components collectively named graphical style. If you have used UI programming toolkits in the past, it is important to note that with djnn you cannot specify the graphical style as an argument of a shape constructor, but that it is made of independent components. It reflects the fact that each style component modifies the graphical context within which all following shapes are drawn. Thus, if you put a red FillColor in the djnn tree, each following shape will be rendered with a red filling until a new FillColor or a NoFill is encountered in the tree.

1.4.1. Fill and stroke

Shapes and text can be filled (ie apply paint to the interior of the shape) and stroked (ie apply paint along the outline of the shape). A shape may be filled with a simple color (FillColor), a linear gradient (LinearGradient) or a radial gradient (RadialGradient), see Gradients for more details.

The outline can only be painted with a simple color (OutlineColor) but it can be customized with the following components:

  • width (OutlineWitdh)

  • cap style (OutlineCap)

  • join style (OutlineJoin)

  • miter limit (OutlineMiterLimit)

  • dash (DashArray, DashOffet)

1.4.2. Opacity

djnn provides a way to assign opacity values to fill and stroke so that the underlying graphics show through when you draw your shapes or images.

You can set the opacity of an object by adding an opacity component for the fill (FillOpacity) or the stroke (OutlineOpacity).

Example: fill opacity 100% (default) and 50%

Upcoming C code

Opacity is a number between 0.0 and 1.0. Whereas a new fill or stroke colour encountered in the tree replaces the current fill or stroke colour in the graphical context, opacities are compounded by multiplying the current value with the new one.

1.4.3. Gradients

Perception is based on contrast, and color contrast is the easiest to create. Designers often want to use gradients rather than mere color juxtaposition. Gradients consists of continuously smooth color transition along a vector from one color to another, possibly followed by additional transitions along the same vector to other colors. djnn provides 2 types of gradients:

  • linear gradient (LinearGradient),

  • radial gradient (RadialGradient),

As in SVG, it is possible to add stops for each kind of gradient.

upcoming C code

Example of gradients

1.4.4. Patterns

Filling with patterns is not yet implemented

1.4.5. Tiled images

Tiling is not yet available.

1.4.6. Fonts

For specifying the font used when rendering text, you can use the same font descriptions as in SVG. This includes font family (Helvetica, Arial, …), font size (12, 24, …), font style (italic, oblique) and font weight (bold, normal, lighter, bolder or a number in the [0, 100] range).

Upcoming C code

1.5. Geometrical transformations

Within djnn, each graphical context has a coordinate system inherited from its ancestor, the root coordinate system being that of the frame. The coordinates of a graphical shape are always relative to those of its context, and the coordinate system of a graphical context is always relative to that of its parent context. By default, a graphical context has an identity transformation attached to it, so that the coordinate system of the parent applies by default.

You can modify the current coordinate system, and thus change the way graphical objects are rendered, by adding geometrical transformations. All geometrical transformations are compounded in sequence; this can be interpreted as each transformation creating a new context for the interpretation of the following transformations and shapes. See below for a note concerning the importance of the order.

djnn offers usual transformations: translation, rotation and scaling. It also offers full homographies (also known as projective transformations), that is general 2D transformations expressed with a 3x3 matrix.

It must be emphasized that transformations act on the relation between two coordinate systems and they do not modify the coordinates of objects themselves, just the way they are rendered. You will see later how this allows you to have the same graphical objects rendered at two different scales in two different parts of your application tree. To figure out what is happening when you apply a transformation, you can imagine that the rendering engine draws each object inside a transparent sheet. To apply a translation, it moves the sheet of the given object; to apply a rotation, it spins this sheet. Similarly for scaling it modifies the sheet of the given component without touching the others sheets.

1.5.1. Translations

A translation has the effect of moving the graphical objects along the X and Y axes of their graphical context.

upcoming C code

1.5.2. Rotations

A translation has the effect of moving the graphical objects along the X and Y axes of their graphical context.

upcoming C code

1.5.3. Scaling

A scaling scales up or down the graphical objects along the X and Y axis of their graphical context. The centre of the scaling can be specified too, it is expressed by its coordinates in the graphical context that applies to the scaling.

upcoming C code

1.5.4. Homographies

You can also perform more complex transformations, such as a mirror effect, by directly manipulating the matrix of an homography. The new coordinates (x2, y2) are derived from an orignal point (x1, y1) as follows:

| x2 |   | a c e |   | x1 |   | a.x1 + c.y1 + e |
| y2 | = | b d f | x | y1 | = | b.x1 + d.y1 + f |
| 1  |   | 0 0 1 |   | 1  |   |       1         |

For example, you can permute the X and Y coordinates with the following transformation matrix

| x2 |   | 0 1 0 |   | x1 |   | y1 |
| y2 | = | 1 0 0 | x | y1 | = | x1 |
| 1  |   | 0 0 1 |   | 1  |   | 1  |

This is expressed as follows:

upcoming C code

It gives the following effect:

Permute X and Y axes

1.5.5. Order of transformations

Each graphical context has a 3x3 transformation matrix. When the object is created, this matrix is set to identity. Later, when a translation (similarly rotation, scaling, skewing) is added to a graphical context, its transformation matrix is left-multiplied by a translation (similarly rotation, scaling, skewing) matrix.

In a composite transformation/sequence of transformations, the order of individual transformation is important because composite transformations are built from right to left (i.e. when a new transformation is applied the current transformation matrix is left-multiply by the matrix of the transformation).

For example, if you first rotate then translate, you get a different result than if you translate, then rotate. Indeed, the matrix produced by the product (translation x rotation) is almost always different from the matrix obtained by the product (rotation x translation)

A rotation of alpha degrees followed by a translation by (dx, dy):

         | cos(alpha)  -sin(alpha)  dx |
Matrix = | sin(alpha)   cos(alpha)  dy |
         |     0            0       1  |

A translation by (dx, dy) followed by a rotation of alpha degrees:

         | cos(alpha)  -sin(alpha)  cos(alpha).dx - sin(alpha).dy |
Matrix = | sin(alpha)   cos(alpha)  sin(alpha).dx + cos(alpha).dy |
         |     0            0                     1               |

1.6. Structuring graphics

1.6.1. Grouping graphics

It is often useful to bundle graphical components together so that they can be manipulated easily as a whole. The main usages are:

  • bundling components together so they can be cloned, hidden, moved and more as a whole,

  • bundling several components together so that they form a new single component composed of several simpler one reacting as a whole to events,

  • interposing a new coordinate system in a hierarchy of components. This can be very useful to manage panning, zooming and other kind of viewing transformation. See Coordinate transformations for an explanation of the transformation system,

  • composing attributes such as opacity with those of their children components (see Opacity for more details),

Grouping graphics in djnn is obtained by creating an empty component and putting graphical components in it. In the example below, we introduce a component named OutlineColor that makes all graphical components in the same group red.

upcoming C code

1.6.2. Clipping your graphics

to be written

1.7. Using SVG files

The GUI module is able to create a tree of graphical components from a file formatted in the SVG format. This means that instead of instantiating graphical objects, resources and transformations with component constructors, you can load them from a SVG file.

When applied to an SVG file, the function djnLoadElement or creates a subtree of graphical components (objects, resources, transformations). You can then use the root of this subtree as a component. Or, taking advantage of the fact that SVG elements can be named, use it as a library of graphical models, using subroutines djnFindElement and djnCloneElement to instantiate the parts of the file you are interested in. See chapter Creating models and instantiating for more details.

2. Animation

to be written

3. Display surfaces

to be written

4. Input devices

This chapter describes the input module, which gives access to components representing input devices and manages device plugging and removal.

4.1. Introduction


5. The GUI module

This chapter describes the djnn GUI library, which implements the classical combination of graphics and certain input devices that was implemented for decades by graphical toolkits: clicking on graphical objects, enter, leave, etc.

5.1. Introduction

to be written

6. Files and directories

This chapter describes how to manage files and directories from your applications. It shows how to be notified of changes made to a given file or in a given directory, how to read the contents of directories and files, and how to create new ones.

6.1. Introduction

Most computers propose a file system as an option for the long time storage and retrieval of data, independently of the type of storage media. File systems usually consist of a hierarchical collection of files and directories, the latter being special files that "contain" other files. The djnn files module allows you to interact with these files, which are considered as components residing outside of the application tree.

In terms of user interaction, files and directories are not exactly an interaction modality. Nevertheless, being notified of their changes can play a part in an interactive application. For instance the file selector on Mac OS X updates its contents dynamically, whenever a file is created or deleted in the directory that is currently displayed. The files module allows you to do that. You might also want to write an application that reacts to the addition of files to a given location by reading them or copying them. In that sense, the file system is as meaningful an input source as a mouse, it is just of a different nature.

One of the peculiarities of the file system is that an application can both create files and discover existing files. Abusing the user interaction terminology, it is both an input and output modality. This will allow us to revisit once again the extended djnn tree, that is the representation of the environment of your application as a larger djnn tree. We have seen in the Displays and Input management chapters how existing components from this extended tree can be used as event sources. Here, we will do that and also create components in the extend tree.

6.2. Locating files and directories

The following example shows how one can access files and directories as djnn components.

upcoming C code

In this example, f and d are components if the corresponding files exists, 0 otherwise. Like input devices, you can add the resulting components to your application tree wherever you want as long as you do not expect them to produce anything when that part of the tree is activated. You will rather want to use them as input sources, or to use their children in connectors.

The naming system used to locate the files is exactly the same as in classical URLs. It works with absolute paths, as in the example obove, as well as for relative paths. In the latter case, the path in interpreted relative to where your program was launched from in the file system. For example, on a Unix-like operating system, if your program is stored as /usr/bin/myapp, you launch it from /home/mydir/mysubdir and you use file:../myfile.svg, this will search for /home/mydir/myfile.svg.

6.3. Reading data

6.3.1. Iterating directories

to be written

6.3.2. Reading files

to be written

6.4. File events

to be written

6.5. Creating files and directories

to be written

6.6. Writing to files

to be written

7. Network interfaces

This chapter describes the network module, which gives access to events occuring on network interfaces of computers: connections/disconnections, network activity.

7.1. Introduction


8. MacBook-specific components

This chapter describes the components provided by the MacBook module, which gives access to MacBook-specific hardware.

8.1. Introduction

The MacBook has platform-specific input and output devices. This module provides input components, that is components that you can discover outside of your application tree, for accessing the input devices. It also provides component types for manipulating the output device, namely the keyboard lighting.

To use this module, include djnn/macbook.h in your code and call djnInitMacBook at the start of your program.

8.2. The CPU sensor

The CPU sensor measures the current usage of the CPU. Currently there is no platform independent sensor. The whole concept of CPU sensing would actually need some additional design since there might be more than one CPU and hence more than one figure. The current figure is an aggregated value over all available CPUs on a Mac. To access the CPU sensor, use djnFindCPULoad. It has one property, named load.

8.3. The SMS event source

Some MacBooks embark a physical acceleration sensor called the Sudden Motion Sensor (SMS). The SMS is accessible as an djnn input component. It has three public properties: x, y and z. To access it, use djnFindSMS:

djnComponent* s = djnFindSMS (); djnComponent* b = djnCreateBinding (0, 0, s, 0, …);

8.4. The light sensor

Recent MacBooks have light sensors that allow them to adapt the lighting level of their display and keyboard. This is represented by an djnn input component with two properties named left and right. To access it, use djnFindLightSensor.

djnComponent* l = djnFindLightSensor (); djnComponent* b = djnCreateBinding (0, 0, s, 0, …);

8.5. The keyboard lighting

You can control the lighting level of the MacBook keyboard by adding a KeyboardLight component to your djnn tree. To create such a component, use djnCreateKeyboardLight. This component type has one property named level.

djnComponent* k = isCreateKeyboardLight (0, "light", 50);

Currently, the original lighting level is not retrieved and consequently it is not restored when a KeyboardLight is stopped. When two such components are present in the tree, the rightmost takes precedence as usual; but once again, nothing happens when the rightmost is stopped. The implementation should be improved.

Beyond the application tree

1. Extension mechanisms

This chapter explains how to create new components or parts of components from existing C code. This is useful for situations not covered by current djnn modules, for managing legacy code and for quick and dirty experiments. The chapter introduces callbacks written in native code, resources managed in native code, component creation, and common operations on the djnn tree available from your C code. It also provides advice about when and how to extend components with C code.

1.1. Introduction

djnn’s model-driven approach creates a border line between two realms. On one side of the border lies what you can express with the models provided by djnn, through components and properties. This sums up to what can be stored as XML files, basically. That is what we have done so far in this guide: with a few exceptions, the only C code you wrote was for adding components to an djnn tree and running it.

On the other side of the border is the realm of the C programming language. While it is desirable to create most of your applications with the elements provided within the djnn realm, that is not always possible and you will sometimes want to add C code.

This chapter explains some of the bridges that cross this border. This includes event callbacks, customisation of component rendering, the creation of components in C code ("native components"), event notifications, and operations on the djnn tree. Other, more complex bridges, are explained in the next chapters.

1.2. Wrapping C callbacks

1.2.1. The NativeAction component

djnn offers various control mechanisms, such as the Binding, the Finite State Machine, the Connector, the Watcher, and the Switch. You can use them to control existing components, but you can also use some of them to transfer control to C code. The trick consists in wrapping your C code into djnn components, and this section explains the most basic wrapper.

The most basic wrapper corresponds to what is usually called the callback: a C function that you define and that the system calls in given circumstances. This is obtained in djnn with the NativeAction component, that wraps a simple C function. Seen from djnn, it is a component. Seen from C, it is a callback function. See for instance how to use it with a Binding:

void dot (djnComponent* e)
 print ".";

void eol (djnComponent* e)
 print "\n";

djnComponent* a1 = djnCreateNativeAction (0, 0, dot, 0, 0);
djnComponent* a2 = djnCreateNativeAction (0, 0, eol, 0, 0);

djnComponent* c = djnCreateClock (0, 0);
djnCreateBinding (0, 0, c, "100", a1, 0);
djnCreateBinding (0, 0, c, "1000", a2, 0);

The first two arguments of the constructor are, as usual, the parent and the name given to the new component. The third argument is the C callback. The last two arguments are discussed further below.

A NativeAction component can be used wherever you would use a component: like here, as the action of a Binding, or as the action of a Watcher or a FSM transition, or even as a child of a Switch or a Component. Just make sure that you understand when these control components activate your NativeAction: the Binding does it upon a given event, the FSM upon a given transition, the Switch upon the activation of a given branch, and a parent Component upon its own activation. add your component as a child.

1.2.2. Retrieving context data

When registering a callback it is customary to provide data that is passed to the callback when calling it. This is the role of the fourth argument in the constructor. However, given how djnn sees C functions as components, this piece of data is not passed as an argument to the function but it is accessible as part of the execution context of the associated component.

You have noted that, in the example above, functions dot and eol have a single argument: a pointer to an element. This is the NativeAction component that was created earlier. Keep in mind that the function is an alter-ego to the component: activating the component means running the function, and vice-versa. And, like other components, the NativeAction component has properties. It has only one, actually, and its name is userdata. You can obtain its value using either the property finding mecanism or the short-cut function djnGetNativeUserData ():

void printchar (djnComponent* e)
  djnComponent* userdata = djnFindProperty (e, "userdata");
  printf ("%c", (char) djnGetPointerProperty (userdata));

void quit (djnComponent* e)
  int value = (int) djnGetNativeUserData ();
  exit (value);

djnCreateNativeAction (0, 0, printchar, (djnUserData) 'x', 0);
djnCreateNativeAction (0, 0, quit, (djnUserData) -1, 0);

Through the general finding mechanism you can obtain more information about the execution context, such as the properties of the component that triggered the activation of your NativeAction component. This is described in section accessing the execution context below.

1.2.3. The "model" flag

When a component is activated, all its children component are activated in turn. This applies to NativeAction children as well. However, you do not always want the callback function to be called as soon as the parent is activated. For instance, if your goal is to store the NativeAction as part of some component so as to associate it to a Binding or a FSM transition, you need a way to prevent its activation from its parent. There are ways to do this within the djnn model, using either exiting components (eg. by putting the NativeAction in a Switch that never changes state) or ad-hoc components (an intermediate component that would store the NativeAction in the parent’s symbol table without creating the implicit parent-child bindings). But doing this every time would be impractical and a shortcut is available in the constructor: the last argument of djnCreateNativeAction is called the "model" flag and tells whether the parent-child binding should be disabled when adding the component to its parent. The name of the "model" flag comes from the idea that such "disabled" components are used as models for further use, including cloning.

In the example below, only callback number one gets activated.

void callback1 (djnComponent* e)
  printf ("1");

void callback2 (djnComponent* e)
  printf ("2");

djnComponent* c = djnCreateComponent (0, 0);
djnCreateNativeAction (c, "callback1", callback1, 0, 0);
djnCreateNativeAction (c, "callback2", callback2, 0, 1);
djnRunComponent (c);

1.3. Wrapping resources managed in C

1.3.1. The NativeResource component

The NativeResource component lies somewhere between the callback and the full-fledged native component described later. It is useful for managing simple resources accessible in C through a pair of activation/deactivation functions and for hooking extra processing to the execution of composite components. A NativeResource is defined by two callback functions; one is called when the component is run, the other when it is stopped. For instance, the following code informs you of when component c is active.

void start (djnComponent* e)
 printf ("hello world\n");

void stop (djnComponent* e)
 printf ("goodbye life\n");

djnComponent* c = djnCreateComponent (0, 0);
djnCreateNativeResource (c, 0, start, stop, 0, 0);

Within the djnn model, these two functions are the RUN and STOP children of the component. Apart from the userdata property, which has the same role as in the NativeAction, the component has no other children. Therefore, you cannot use it to store information, accept input or emit output in a way that is meaningful to other djnn components. Nevertheless, the NativeResource component can prove useful for various situations.

1.3.2. Managing resource state

The NativeResource component is ideal for managing unique resources that have exactly two states, eg. active and inactive. For instance, imagine that some of your components contain power intensive, low priority computations. You may wish to lower the execution priority of your program while these components are running. On a Unix-like computer, you could do as follows to manage the priority of your program:

void lower_priority (djnComponent* e)
  nice (5);

void higher_priority (djnComponent* e)
  nice (-5);

djnComponent* c = djnCreateComponent (0, 0);
djnCreateNativeResource (c, "priority", lower_priority, higher_priority, 0, 0);
djnCreateMyComputationIntensiveComponent (c, "computation");

1.3.3. Adding execution hooks

Using the ability to control the order of children in a component allows you to

void pre_run (djnComponent* e)

void post_run (djnComponent* e)

void pre_stop (djnComponent* e)

void post_stop (djnComponent* e)

djnComponent* c = djnCreateComponent (0, 0);
... // filling component c
djnCreateNativeResource (c, "</pre", pre_run, post_stop, 0, 0);
djnCreateNativeResource (c, ">/post", post_run, pre_stop, 0, 0);

Note that the above example does not fare well with further additions to component c. In particular, adding a new child without specifying where will add it at the end of the list of children, that is after post_run, which is not what you want. It is up to you to specify "<post/" when adding new children. Of course this requires that you have access to the code that adds the new children, which excludes third party component.

This technique does not work either with third-pary components that do not accept additions because their interface has been restricted. For that, you will need extension mechanisms that operate on the execution ordering within the djnn rendering engine and not only within components. This is similar to adding a new rendering engine that renders existing components, or extending a rendering engine.

1.4. Creating native components

(ecrire un petit texte introductif et un exemple, le reste etant decrit dans le chapitre suivant)

1.5. Operations in C code

When writing a plain callback (for Bindings and FSMs), a watcher callback (for watching properties) or the rendering or your own components, you can trigger whatever side effects you want outside of the djnn tree: writing to a file or a console, updating a data base, etc. You may also want to apply operations to the djnn tree, emit events, or propagate a data flow.

1.5.1. Retrieving the execution context


A callback used as an action in a Binding has access to information about why and from where it was called. The information is obtained with accessor methods defined in the NativeAction class. The most interesting information for callbacks used in Bindings is:

  • the source of the event, obtained with

  • arguments passed when using an "array-style callback" as the action, obtained with

  • source-specific arguments, obtained with

A callback used as an action in a Watcher has access to information about why and from where it was called. The information is obtained with accessor methods defined in the NativeCode class. The most interesting information for callbacks used in Watchers is:

  • the owner of the changed properties, obtained with

  • a test that says whether a property has changed:

1.5.2. Modifying the djnn tree


You may want to modify the djnn tree. The available operations are:

  • adding an element to the tree.

  • removing an element from the tree.

  • moving an element in the tree, so as to change its grouping, its rendering order, or its rendering context.

  • changing properties in djnn elements. See paragraph Updating properties below.

1.5.3. Emitting events

We have seen that djnn modules each bring their own components that can be used as sources: windows, graphical objects, clocks, etc. But this is only the beginning of the event chain in your application: you want your own components to emit events, so that other components can in turn react to them, and so on.

For this, you need to remember the rules of bindings: the action is activated whenever the source (or more precisely the element designated in the source by the detailed specification) is activated. So, in order to allow other components to bind to your component, you just need to provide them with elements they can bind to, and to have them activated when appropriate. This starts with your component itself: it is a legitimate source and you can create bindings on it. But very often you will want to create specific children in your component.

The example below shows a component that contains a rectangle and an empty component press that is activated when the rectangle is pressed. In other words, a "press" on the button is a "press" on the rectangle.

Upcoming C code

This example is very simple and we will see later that there is a simpler way to just re-export an element like our rect/press here. But it is rare that you want to give the exact same name, or even the exact same structure, to your public event sources. In many cases you will want to use more complex components than this: an OR component that is activated whenever one of several components is activated, a state or a transition of a state machine, etc.

1.5.4. Updating properties


Your components can change the state of properties.

1.5.5. What extension strategy?

Several options are available to you when you decide that you need to create extensions. The following options are listed to help you make your decision.

  1. No extension. Maybe you do not need to create extensions at all, and you can do with assembling existing components. Please take a minute to think it over before writing an extension. Bear in mind that code with extensions will be harder to port to other djnn rendering environments: you lose the benefits of model-driven development. Have you looked at the examples provided with djnn? Maybe you can do without an extension.

2. Building native components

2.1. Introduction

Native components are components whose internals are not built with djnn but directly in the host language, here in C. This includes components provided by djnn modules: rectangles, bindings, and so on are native components. In this chapter we will see how you can build your own native components.

With bindings and native actions, it is already possible to create components whose actions are implemented directly in C. However, for performance reasons djnn uses and offers an additional mechanism, reminiscent of the implementation of classes in object oriented languages:

  • in addition to the name-based mechanism to access children components (actions), native components have an table-based mechanism. They can be considered as pre-compiled components, that is components whose original code was transformed into a less generic but more efficient form.

  • the action tables of native components are shared by all components of the same type, which thus constitute a class. The generic mechanism in djnn for creating similar components is cloning. Here, we do not need the ability to modify cloned components; the shared action table corresponds to this situation.

Concretely, the example below shows how to add two actions run and stop to all instances of class djnThingy. djnRUN and djnSTOP are two constants defined for the whole djnn environment. Defining further constants is your responsability if you want more pre-compiled actions in your module. Note that class djnThingy must be initialised before any of its instances is used.

static void djn_RunThingy (djnComponent*);
static void djn_StopThingy (djnComponent*);

djnCompiledAction djn_ThingyCompiledActions[] = {
  /* djnRUN */          {"run", djn_RunThingy},
  /* djnSTOP */         {"stop", djn_StopThingy},
  /* end of list */     {0, 0}

djnClass djnThingys = {"thingy", 0, 0, 0, 0, 0, 0};

djn_DummyInit ()
  djn_InitClass (&djnThingys, djn_ThingyCompiledActions);

djnDummyCreateThingy (...)
  djn_SetComponentClass (e, &djnThingys);

2.2. Accessing semi-public declarations

Components and properties are manipulated through the semi-opaque type djnComponent. By "semi-opaque", we mean that application programmers should consider these types as opaque and module programmers can consider them as open. This distinction is enforced by the distinction between two headers:

  • djnn/core.h contains all the declarations needed by application programmers This header file makes the types opaque.

  • djnn/core-dev.h contains the additional declarations needed by module and API programmers.

2.3. Creating a native component

Application programmers use the constructors of components provided by modules, for instance djnCreateRectangle for graphical rectangles. The only point worth noting here is that all of these constructors return a djnComponent, which can be used for all functions described in this chapter and others.

Other programmers need a function to create or initialise a component. Currently, the recommended practice is to allocate the memory space and to use djn_InitComponent. More specifically, all C modules currently use the pattern shown below:

struct djnMyComponent {
  djnComponent component;
  int myData;

djnCreateMyComponent (djnComponent* parent, const char* name, other arguments)
  djnMyComponent* me = (djnMyComponent*) malloc (sizeof (djnMyComponent));
  djn_InitComponent (&me->component, 0, 0);
  djn_FinalizeElement (&me-component.element, parent, name);
  return &me->component.element;

2.4. Defining the actions of a native component


2.5. Deleting a native component


3. Writing a module

Chapter summary

This chapter describes how one can create a library implementing a new djnn module.

Chapter contents

3.1. Introduction


4. Parsing XML files

4.1. Introduction

To be written

4.2. Initialising and parsing file

djn_InitXML, djnParseXML

4.3. Creating a parser

If you introduce new component types in your own module or application, you will need to build the corresponding XML parser and register it with djnn so that programmers may refer to your components in their XML files. If you are just creating an application and want to have your own independent XML format, you can also choose to use part or all of the djnn XML parsing system to make your task easier. Let us see how.

The types and functions described below are declared in djnn/xml-dev.h.

4.3.1. Building and registering

Registering a parser with the djnn XML subsystem consists in passing an XML namspace identifier and a lookup function to djn_RegisterXMLParser. After your parser is registered, every time the djnn XML parser finds a tag prefixed with the namespace, it uses the lookup function to process the tag. The namespace identifier can be something like http://www.w3.org/2000/svg or mynamespace as long as it is unique.

The lookup function is the entry point to your parser. You can build it yourself, or you can take further advantage of the djnn XML subsystem:

  • the djnn XML subsystem relies on the GPerf hash table generator. You can build your own parser the same way, by creating a GPerf file such as the one in the example below. A GPerf file has three sections. In the first, put your declarations and end with an empty reference to struct djn_XMLTagHandler. In the second section, create one line with each tag name and the address of two functions that will be called when the tag starts and ends. In the third section, put your functions. The lookup function will be created by GPerf, and you can choose its name when running GPerf.

  • if you decide to create it yourself, your lookup function must accept a tag identifier (without the prefix) and its length, and return a pointer to a struct djnTagXMLHandler. This struct contains a field named start and one named end, which pointers to functions that are respectively called when the corresponding tag starts and ends.

Whichever of the above you choose, your start functions must accept a pointer to an array of text strings and one to a component, and return a pointer to a component. The text strings are the attributes and their values in succession (key, value, key, …). The received component is the current active component being filled. The returned component is the new component if one is created, or the received component if you decide not to create one. The end function only receives the current active component. If you had created a component in the start function, it is this one and you should return its parent; in the other case, just return what you received.

The start function is usually where you create the I* component that corresponds to the tag. It is also the place where you need to handle the attributes.

4.3.2. Handling attributes

Each tag type has a set of possible attributes. Some are mandatory and some optional, among which some can have default values. XML has an attribute inheritance system between tag types: if tag B inherits from tag A, it has all attributes of tag A. It is up to you to handle the received attributes according to this. You can do it yourself, or you can use the djnn XML subsystem a bit more.

The djnn XML subsystem proposes function djn_XMLHandleAttr to help you handle attributes. This function accepts as its first argument a pointer to a pointer (i.e. a handle) to a component; this is meant so that you can pass a component to it and/or return a component from it. Using this, you can:

  • either create first the component corresponding to the tag, then call djn_XMLHandleAttr successively for all received attributes;

  • or decide that some attributes mean that helper components should be created. You must then add a holder component to store the helper components and the component corresponding to the tag. You can also delay the creation of the component corresponding to the tag, so that you are sure that it comes after the helper components (or you could do the opposite, of course).

In addition to the handle and a pointer to the remaining array of attributes, djn_XMLHandleAttr accepts a list of lookup functions. Each such function corresponds to a set of attributes. They are called in sequence until one has a match, thus implementing the desired inheritance semantics.

You can implement your own lookup functions or do as the djnn XML subsystem which relies on GPerf once again, with one file per set of attributes. End the first section of your file with a reference to struct djn_XMLAttrHandler. In the second section, list your attributes and one handling function for each. In the third section, implement your handling functions. GPerf will create the lookup function for you.

The handling functions will be called when the corresponding attribute are encountered. Each must accept a pointer to a component and one to a text string, and use the text string as the value of the attribute.

4.3.3. Example

The following code shows how to build a parser for module MyModule which contains two tag types named MyComponent1 and MyComponent2, using GPerf.

This is the gperf-based module parser. It has two lines because there are two tag types in our module. We use the two tag types to show different ways of handling attributes: those that we want to add as properties after the component described by the tag was created, those that we want to use as arguments to the constructor of our component, and those that we want to use as a trigger for creating a component and additional components around the component described by the tag.


#include <djnn/xml-dev.h>

static djnComponent* StartMyComponent1 (const char**, djnComponent*);
static djnComponent* StartMyComponent2 (const char**, djnComponent*);
static djnComponent* EndMyComponents (djnComponent*);

extern struct MyComponent2Args {int u;} MyComponent2Args;

struct djn_XMLTagHandler;

myelement1, &StartMyComponent1, &EndMyComponents
myelement2, &StartMyComponent2, &EndMyComponents

/* a tag that we convert into a component then add properties */

static djnComponent*
StartMyComponent1 (const char** attrs, djnComponent* current)
  djnMyComponent *e = CreateMyComponent1 (current);
  while (*attrs) {
    if (!djn_XMLHandleAttr (&e, attrs, MyComponent1SymLookup, djnComponentSymLookup))
      fprintf (stderr, "unknown attribute '%s' in my component 1\n", *attrs);
  return e;

/* a tag that may contain attributes that trigger the creation of a
  holder component in which we store helper components in addition to
  the component associated to the tag */

static djnComponent*
StartMyComponent2 (const char** attrs, djnComponent* current)
  djnComponent *e, *holder = 0;
  MyComponent2Args.u = 0;

  while (*attrs) {
    djnComponent *before = holder;
    if (!djn_XMLHandleAttr (&holder, attrs, MyComponent2SymLookup, djnComponentSymLookup))
      fprintf (stderr, "unknown attribute '%s' in my component 2\n", *attrs);

    /* if a holder component was created, add it to the current component */
    if (before != *holder)
      djnAddChild (current, holder, 0);


  e = CreateMyComponent2 (holder ? holder : current, MyComponent2Args.u);

  return holder ? holder : e;

static djnComponent*
EndMyComponents (djnComponent* e)
  return e->parent ? e->parent : e;

Then this is the gperf-based attribute handler for our first tag type.


#include <djnn/xml-dev.h>

static int HandleMyAttribute (djnComponent**, const char*);


struct djn_XMLAttrHandler;

myattribute, &HandleMyAttribute

static int
HandleMyAttribute (djnComponent** e, const char* value)
  /* <do something with e and value here> */
  return 1;

Then this is the gperf-based attribute handler for our second tag type. There should be one such file for every tag type:


#include <djnn/xml-dev.h>

static int HandleMyAttributeA (djnComponent**, const char*);
static int HandleMyAttributeB (djnComponent**, const char*);


struct djn_XMLAttrHandler;

myattributeA, &HandleMyAttributeA
myattributeB, &HandleMyAttributeB

struct MyComponent2Args MyComponent2Args = {0};

/* an attribute that we want to handle as a component */
static int
HandleMyAttributeA (djnComponent** e, const char* value)
  if (*e == 0)
   *e = djnCreateComponent (0, 0);
  /* <analyze value here> */
  djnCreateMyComponentA (*e, 0...);
  return 1;

/* an attribute that we want to handle as an argument to a constructor */
static int
HandleMyAttributeB (djnComponent** e, const char* value)
  /* <analyze value here> */
  MyComponent2Args.u = ...;
  return 1;

This is how you register your parser with djnn. Call this when your module is initialised.

  djn_RegisterXMLParser ("http://your-namespace", &MyModuleSymLookup, "MY");

Finally, this is how you run GPerf on your files from a Makefile:

gperf -t -n MyModuleSymLookup MyModule.gperf > MyModule.c

5. Interacting with the operating system

This chapter describes how to use the djnn system hook (aka main loop) in your djnn modules.

5.1. Introduction

When building modules that provide new input sources or new output channels, your code will often need to interact with the operating system in an asynchronous fashion. It will wait for input or network messages, periodically poll microcontrollers, wait for system replies, for process completion, etc.

The job of djnn’s system hook is precisely to wait for that kind of events from the operating system, and to bind them to djnn event sources (and with most djnn APIs, programmers must explicitly run it at the end of their main programs when it is needed, because there is no way for the djnn execution engine to do it transparently). The first section of this chapter explains how you can ask the system hook to track standard system sources for you, and how you can extend it with new system sources.

Another option for you is to create a thread dedicated to waiting for a given type of events. If you use an external library for detecting events, it might actually do it for you whether you agree or not. In that case you will to propagate events back to the main thread. The second section of this chapter explains how to do it through the djnn system hook.

You may also want to execute some tasks asynchronously without creating threads; there again, the djnn system hook offers support for this.

The djnn system hook was derived from the Tcl main loop. Tcl programmers will therefore be in a familar environment. This also means that the documentation of Tcl internals may be a useful reading as a complement to this chapter.

5.2. Using system sources

The djnn system hook offers system-level sources that you can use to build your own components.

5.2.1. Files and sockets

You can have the system hook call a function of your own (callback function) when a file or socket is ready for reading or writing:

  • use djn_CreateIOHandler to register your I/O channel. This function takes as arguments the platform dependent channel descriptor (Unix file descriptor, Windows handle), the subscription mask (read, write, exception), the callback function, and some arbitrary data (pointer or integer number) that you want to be passed to the callback function whenever it is called.

  • use djn_DeleteIOHandler to cancel a IO channel subscription. This function takes as only argument the registration token that was returned by djn_CreateIOHandler.

5.2.2. Timers

  • use djn_CreateTimerHandler to have a callback called after a given number of milliseconds. The result can be stored to cancel the registration.

  • use djn_DeleteTimerHandler to cancel a timer registration

5.2.3. Child processes

Not available yet.

5.2.4. Signals

Not available yet.

5.2.5. Creating custom system sources

The djnn system hook has an extension system that allows you to make it manage your own system-level sources. Custom system sources consist of two callback functions that are called before and after entering the code that waits for standard system events. They are created and installed in the system hook by calling djn_CreateSystemSource, and uninstalled and deleted with djn_DeleteSystemSource, with the two callback functions as arguments.

The two functions should have the following signature:

typedef void (djn_SystemSourceProc) (djnUserData clientData, int flags);

You can use them in various fashions, for instance:

  • In the first function, ensure that a standard system source will wake the system hook out of its waiting state whenever an interesting event occurs. then in the second function detect interesting events and propagate them through djnn components. This is how the timers are managed.

  • If there is a non-blocking way of checking for pending events, use it in the first function check, make sure with djn_SetMaxBlockTime that the system hook does not wait at all if there are, then propagate them in the second function. This is how some display events are managed.

  • In the first function, ensure by calling djn_SetMaxBlockTime that the system hook will not wait for more than a certain amount of time; then in the second function detect interesting events and propagate them through djnn components. This polling method is unfortunately necessary for some system sources.

Here is an example, using the second method:

static void mySetupProc (djnUserData data, int flags)
  if (events_ready ()) {
    djnTime blockTime = { 0, 0 };
    djn_SetMaxBlockTime (&blockTime);

static void myCheckProc (djnUserData data, int flags)
  event* ev;
  while (ev = next_event ())
    djnNotify (find_component (ev));

void myInitModule ()
  djn_CreateSystemSource (mySetupProc, myCheckProc);

5.3. Managing system sources from other threads

If your code (or the code of a library that you are using) involves threads to detect events, you will want to propagate these events back to the thread where djnn components reside. Failing to do so, that is propagating events to components directly in the detection thread, may work for simple tasks. But it will probably fail when complex rendering engines, such as graphics, are involved: race conditions will occur on the data used to manage rendering.

Threads are executed asynchronously, so you need to communicate from the detection thread to the other thread through the system hook of the second thread. This is done by:

  • creating a function that will propagate the events from the main thread;

  • allocating data structures named events that contain the information gathered by your detecting thread

  • queueing the data structures in the system hook of the main thread, using djn_ThreadQueueSystemEvent from the detecting thread;

  • alerting the system hook of the main thread that it must stop waiting and handle these events, using djn_ThreadAlert from the detecting thread.

Your event structures must start with a struct djnSystemEvent field, and a pointer to the event handling function must be stored in its proc field. Here is an example:

typedef struct my_event {
  djnSystemEvent event;
  int my_data;
} my_event;

static int
my_event_proc (djnSystemEvent *ev, int flag)
  my_event* mev = (my_event*) ev;
  djnNotify (my_find_component (mev->my_data));

static void
my_system_source_check (...)
  my_event* mev = (my_event*) malloc (sizeof (my_event));
  mev->event.proc = my_event_proc;
  mev->my_data = ...

  djn_ThreadQueueSystemEvent (my_main_thread, &mev->event, DJNN_QUEUE_TAIL);
  djn_ThreadAlert (my_main_thread);

5.4. Background tasks

The djnn system hook offers a way to have some code executed by the system hook when no low level event is pending. This is obtained by calling function djn_DoWhenIdle and passing it the address of a function you want called. You can use it for background non-interactive tasks (loading a file, doing a large computation), provided that every call to the function you passed is short enough to keep the application able to react to the next event. Or you can use it in your implementation for postponing some actions until after the handling of the current asynchronous low-level event. This is usually not a good idea because you lose control on when your action will actually be performed, but you can use it if you know what you are doing.

You can also decide to make a version of this available to the end-programmer, so as to provide a thread-like feature as an extension to the I* model.

Whatever you do, be aware that the relation between this feature and the execution semantics of djnn has not yet been clarified and documented, and that this can lead to a behaviour incompatible with what the end-programmer expects. You are welcome to offering the clarification, of course.

Wrapping the libraries in a new API

1. Wrapping components

This chapter describes how to make component management available through a new programming language.

1.1. Introduction

djnn aims at being a purely declarative framework for building interactive programs. Therefore, the most important programming activity with djnn consists in instantiating I* components and assembling them together in a "tree". The leaves of the tree are atomic components, and its other nodes are composite components. Atomic components are for instance graphical components (rectangles, circles), behaviour components (state machines, event bindings, data-flow connectors), or computing components (assignments). Composite components contain other components. Among them, components are the most straightforward: they just gather children components and make sure that they enjoy the same life cycle. More subtle composite components are for instance switches, of which only one child is active at any given time.

When building interactive applications or components with djnn, there are few functions to use. Basically, you would need:

  • constructor functions for every type of component. These functions will be introduced with every module; let’s just say that they are all called djnCreateSomething (where something is the name of the component type), and all return a pointer to the opaque struct djnComponent.

  • functions for locating, addressing, and otherwise referring to components already present in the tree.

  • functions for manipulating components: adding children and properties of course, but also tuning the component’s interface so that it publishes exactly the children and properties that you want.

When developing an djnn API in another language you need to provide methods for the above tasks. The djnn C libraries provide you with functions to do this, with the hope to support all your possible needs: initialising an existing component structure, manipulating the inteface of an atomic component, etc. In addition, you will need to access the internals of types djnComponent, djnProperty and djnComponent, which will no longer be opaque to you.

2. Wrapping the system hook

This chapter describes how to wrap the djnn system hook (aka main loop) in your djnn implementation, and what you should make accessible to "end-programmers" through your API.

2.1. Introduction

The djnn system hook is the very core of (nearly) any djnn program: it is the chunk of code that ensures that your application continuously reacts to external signals: actions on devices, timers, network communications, etc. Therefore, you’ll have to use it in your API to implement the run phase. You’ll also need to allow your end-programmers to create their own external sources (platform-dependent inter-process communication, signals, who knows what) and to connect djnn with another framework in their application (to reuse Gtk widgets, for instance). This chapter gives a few principles that will help in these tasks.

2.2. Initializing and running

First of all, djn_InitSystemHookHere () must be called once for each thread in which the system hook is accessed. In the main program, you just need to call djnInitCore (). As an example, in the Perl djnn API this call is made just after loading the core C library. In another implementation and/or in secondary threads, you may have to do it in a function that the end-programmer will be requested to call.

Then at some point you need to enter a looping phase. In the Perl API of djnn, it is an explicit call that the end-programmer makes: SystemHook::run. In another implementation you might be able to make this implicit.

Please note that, in the absence of any subscription to any asynchronous source (i/o channel, timer, etc), the system hook will iterate very fast and create what is called an active wait. In the Perl djnn API the situation was deemed rare and trivial enough to not necessitate a special treatment, but you might rule otherwise.

2.3. End-programmer API

Your end-programmer may need to perform all kinds of tricks in his or her djnn application: create new sources based on currently unsupported network communications or low-level mechanisms, use one or more other programming frameworks, use a library that is made for another framework, etc. Therefore you need to provide ways of accessing the system hook for that.

2.3.1. Building new sources

To build a new source, the end-programmer will be exactly in your situation when you build a new source. You can therefore decide to just document how to use the djn_Create\*Handler and djn_Delete\*Handler functions in C. However, that will require some C programming skills from the end-programmer, and his or her ability to establish a bridge between the C data and the data accessible in the target language. Don’t forget also to document the use of djn_InitSystemHookHere, just in case.

You can also provide an encapsulation of these functions in the target language. The burden of building tyhe bridge between C and the target language will then be yours. You can refer to the djnn::SystemHook package in the Perl djnn API to see how this was done.

2.3.2. Connecting with another framework

Mixing djnn code with code using graphical toolkits or user interface environments usually implies playing with the main loops of both environments. Usually, the other main loop will be accessible in C, so we advise you not to try and provide an API in your target language.

There are two possible choices: plugging the other main loop onto djnn, or the reverse. What you can do is document both operations by stealing text from section Accessing the system hook in C in the djnn Perl API Programmer's Guide, and/or from Tcl documentation.

3. Supporting module development

Chapter summary

This chapter describes how an djnn API programmer can make it possible for other programmers to build an djnn module in the host language of the API (C++, Perl, Python, etc).

Chapter contents

3.1. Introduction