Perspective from Japan on whaling and whale meat, a spot of gourmet news, and monthly updates of whale meat stockpile statistics
This article has been posted
HERE at
edevelop.org, and will be maintained at that location going forward. This article remains here for historical purposes (^_^)
What this is:These are my notes about the creation of a module for e17, gathered together in an attempt to provide a semi-tutorial. This is not a step by step instruction guide on how to make a module - just points that I found pertinent while doing so myself. I do have a background in software (BSc Computer Science) and experience with C (in a kickass 3rd year networking paper), and expect anyone reading this and hoping to find this informative would have some C knowledge as well. In this article our focus is on the C source code for a module - we won't be going into the details of makefiles or edc's for edjes this time.
Also note that this is not the article of a seasoned e17-pro, just someone doing this in his free time for a bit of fun. Don't take anything written here as 100% truth.
Everything in this article with regard to CVS is up-to-date at the time of writing (to the best of my knowledge), but as development progresses this article will possibly fall out of date.
Finally, anything that happens to your computer as a result of your reading and using this information is your responsibility, not mine! If you start fiddling with your module source code it's quite likely that you'll crash the whole window manager and any open apps if you do something silly - I've done this numerous times myself :-)
That's the disclaimer out of the way...
Motivation:After my
initial pathetic contribution to E17, an actually
useful patch more recently, as well as
Japanese interface translation, I've now read enough cvs commits and emails to have a bit of a clue about how enlightenment 17 works. Thus I decided it was high time I hacked up a module of some sort to learn some more, and maybe even contribute something somebody else might like to use.
But what should I make?
The first time I used
Enlightenment would have been around 2 and half years ago when I was trying out a range of "light weight" linux distros on a crappy old half (now almost completely) broken Pentium 166 with maybe 32Mb of RAM. My favourite distro at the time (visually, at least) was
Peanut Linux (now aLinux) and that was due entirely to it having Enlightment 16 as it's default window manager. One of e16's features are small little mini applications known as "epplets", and my favourite was and still is E-MoonClock. All it does it sit on your desktop and display the current phase of the moon. Very simple, and pretty useless really, but I like it and happily grant it some of my RAM and CPU cycles.
Alas, e17 doesn't have it's own moonclock. Yet with the source to e16's E-MoonClock
available, plus a ready made
set of phased moon images, there would be few modules simpler to slap together than this one. So, let's go.
The e17 moon clock module source:Whether you plan to read this article or not, the source for the e17 moon clock module that I'm writing about in this article can be obtained from here:
http://www.geocities.jp/david_at_tokyo/e17/moon/1) Prerequisite: The Enlightenment 17 Module APIEnlightenment 17 has a pre-defined interface consisting of five C functions which modules are required to provide the definition for. This interface is very simple:
EAPI void *e_modapi_init (E_Module *m);
EAPI int e_modapi_shutdown (E_Module *m);
EAPI int e_modapi_save (E_Module *m);
EAPI int e_modapi_info (E_Module *m);
EAPI int e_modapi_about (E_Module *m);An introduction to implementing the bare minimum for a module is
available here at essiene's
"Exploring E17 Modules - Part 1". Since the basic module API has good coverage at that tutorial already, I'm not going to write any more about it. If you haven't read that article already, you probably should before reading this one, unless you are happy just to plagarise these function definitions from an existing module.
2) The User Interface - Edje (display only)I'd been reading about the concept of
Edje based applications prior to commencing the e17 module fun, and found the concepts very appealing, so much so that I wanted to give it a blast for myself. The Edje library makes it possible to seperate your application's user interface from the less visible application code, rather than having them tightly integrated together. With Edje you have the freedom to ship details of the way your interface looks and behaves into an independant source file, which is made up of details of the interface layout, images, and mini programs to control what the interface does. To be honest I got sick of building interfaces when using Delphi previously, prefering to work on non-user visible internal source code, but so far the Edje learning experience has been enjoyable. Anyway, my goal was to try the Edje library out for myself, and see how the separation of UI details from the real source went.
3) How to make a moonclock tickBut that left the problem of figuring out what the phase of the moon is at a given instant... any takers? OK! I don't have a clue about how to calculate the current phase of the moon, and I'm guessing the majority of people who will read this aren't astronomers either. Thankfully, on brief examination of the
E-MoonClock source, I found that in fact it's author had in turn borrowed a chunk of source code from a prior moonclock author, who was rightly credited with doing all the real work. Likewise, I am equally indebted, but let's continue.
To create the moonclock all one needs to do is set up a couple of time & date arguments, call a
library function, then use the resulting floating point value (between 0.0 and 1.0) to decide which moon image to display (and repeat the process periodically to keep the moon image up to date). The original E-MoonClock does all of this in one function (very simple).
Images however are a feature of the user interface with nothing to do with the application code, and in the spirit of Edje, we'll be doing things a bit differently. E-MoonClock has 60 smallish moon images (and the epplet source code is specific to having 60 images), but if someone else out there had a larger set of higher quality images available that they wish to use, they'd have to recompile the entire source to do so. That's not cool - ideally we want to maintain the separation of moon phase calculation and other stuff from the UI details. So the ultimate goal is to slap together the C code for the module, define the interface details in an Edje source file (EDC) and somehow have the application code inform the Edje of the current phase of the moon as represented by a floating point value. Other than this floating point value, there's nothing else the Edje needs to determine how to represent the moon phase information on screen.
4) Plagarising the skeletonSo into the C code we go. But where to start? There's a saying about lazy programmers (and this whole module is a fine example), and in that tradition I went looking for a module that was similar enough to my own. Basically I just want to create a square moon clock like that in e16 - in some respects very similar to the e17's default clock module. Like the clock, the moon module just needs to sit there on the screen, maintaining it's aspect and so on, with the addition of sending moon phase information to the moon modules' Edje (this is the main difference to clock, and we'll cover how to make our module do this later). So, I followed the clock module source closely, copying stuff that I needed (and in some cases copying stuff just to figure out what it actually does). If you haven't read the
e17 clock source before, now's a good time to open it up and have a squizz.
The definitions of the moon module's five module API functions were basically copy and pastes of the clock module code with a few changes to refer to moons instead of clocks.
The init function is the only function that does much in my case: the actual creation of a "Moon" data structure, which I eventually separated out into a helper function, "_moon_new" ala the clock module.
5) The Moon data structure, _moon_new and _moon_freeThe Moon data structure in src/e_mod_main.h only contains three things:
typedef struct _Moon Moon;
struct _Moon
{
Evas_List *faces;
E_Menu *config_menu;
Ecore_Timer *moon_check_timer;
};
a) A list of moon "faces".
The "Moon_Face" data structure holds information about which canvas it's to be included on etc. The Moon data structure has a list of these, as Enlightenment doesn't assume that you have only one screen for your computer (I only have one myself, so haven't bothered myself with the details, and could be only 50% on the mark about this - hopefully someone will confirm or correct me)
b) A menu data structure.
All modules have an entry in the "Modules" menu, so why not have one for the Moon module as well? We'll have a familiar use for this later anyway: configuration - but not in this article. At the time of writing the configuration menu in the e_modapi_init function is also disabled since we aren't using it yet.
c) An Ecore_Timer.
We need to periodically check the current phase of the moon so that we can keep our UI Edje up to date with the latest information. Rather than get dirty with SIGALRM's, the Ecore library already provides a simple and easy to use timer mechanism for us to take advantage of. The Ecore_Timer allows our module to receive a callback from the underlying Enlightenment infrastructure after a certain amount of time has expired. Callback's (often abbreviated as "cb"s) are something that pop up all over the place in Enlightenment - these are just event listeners. e17 overall follows an event-driven model. The EFL Cookbook
has an example of one of how to use Ecore_Timers if you need some more detail, but in this case we are going to do this:
moon->moon_check_timer = ecore_timer_add(60.0, _moon_cb_check, moon);
This sets up our Ecore_Timer to call us (the moon) back via the _moon_cb_check function, every 60.0 seconds. I'll talk more about the _moon_cb_check function later, but for now I'll just note that the function
must return a value of 1 in order for the timer to be renewed after each call to the callback function (or so I hear).
The _moon_new function sets up these three items, and since this is C, we also write an accompanying shutdown function (_moon_free) to make sure we free'd all the memory we allocated in _moon_new.
If you compare these moon functions with those in the clock, you'll notice the moon functions are simpler - first off I just want to get this thing working, so there is no code to save configuration information yet, and the Ecore_Timer time value is hardcoded to go off once every minute. Later on (not in this article though), we'll make this configurable, but for now once a minute should be acceptable.
6) _moon_face_new and _moon_face_freeThe _moon_face_new function is more interesting than what we've seen already, with a range of EFL and Enlightenment functions being invoked. This time we're setting up a Moon_Face data structure that is defined something like this:
typedef struct _Moon_Face Moon_Face;
struct _Moon_Face
{
E_Container *con;
Evas_Object *moon_object;
E_Gadman_Client *gmc;
};
The first thing we do is allocate memory for the moon_face structure, and then assign the structure's Container value with the container that the moon_face is going to be added to. At this stage we don't actually need to record this container in the object, but (I think) we'll find it useful later when we add some configuration save / load code.
Next up it's time to setup our moon's Edje object! This involves carving out a chunk of space on the background canvas for the moon module to display it's interface in. It's worth noting here that with the Enlightenment Foundation Libraries and evas in particular, the model used is of a "canvas" containing objects, which can then be manipulated (moved, resized etc). This is what we have with visual modules in E17 - they are basically objects etched into the desktop background. This is about as far as my rudimentary knowledge of evas goes, but as we'll soon see it's enough to get our module working.
I presume there's a good reason for it, but we start this activity by first calling
evas_event_freeze, passing the background canvas of the Container that this face will be added to as the required argument. At the end of the function, once we've finished setting up the Moon's interface, we'll call the corresponding function
evas_event_thaw again specifying the same background canvas.
evas_event_freeze(con->bg_evas);
// make changes to background canvas (i.e., add new object to represent moon etc)
evas_event_thaw(con->bg_evas);
After calling evas_event_freeze and until evas_event_thaw is called, the background canvas specified puts all event processing on ice. This is the time to make changes to the canvas, that is, adding edje object's to it, positioning them and so forth (see below). Presumably bad unpredictable stuff could happen if the evas is processing events (maybe objects moving about etc) while we are making our own changes to the canvas at the same time - the mind boggles! I'm not sure about exactly what scenarios this functionality prevents - hopefully I'll get some more detail on this later, but for now, as the clock and various modules do this, we will too.
Now that the events from the background canvas are on hold, we
add a new edje object (our moon's "face" on the desktop) to the background:
o = edje_object_add(con->bg_evas);
face->moon_object = o;
We then tell the new Evas object which user interface file to use. For the moon module we'll be using a file called "moon.edj", but we'll not be getting to that until we've finished writing the C code. In the meantime, let's just assume we have a dummy "moon.edc" file, which will be compiled into "moon.edj". Later on we need to write "moon.edc" properly so that it'll display moon images, but for starters we'll just use a plain black rectangle:
collections {
group {
name: "moon/main";
parts {
part {
name: "dummy";
type: RECT;
description {
state: "default" 0.0;
color: 0 0 0 255;
rel1 {
relative: 0.0 0.0;
}
rel2 {
relative: 1.0 1.0;
}
}
}
}
}
}
}
We use
edje_object_file_set to specify that the object we added to the background canvas should use the "moon.edj" file. If you look at other modules, you'll see them using a semi hardcoded path name to the edje file here. If you mimic such modules, you'll probably come up with something like:
edje_object_file_set(face->moon_object, PACKAGE_LIB_DIR "/e_modules/moon/moon.edj", "moon/main");
This is pretty bad actually (as I discovered myself), because we don't know that the module is going to be installed under "e_modules". e17 currently supports modules that have been installed into user home directories at ~/.e/e/modules/
as well. If the user chooses to install there instead of under the e_modules tree, when the user enables the module and it tries to load it's edje, it will not be able to find it, and will choke up errors all over your standard output. So, we really want to find our moon.edj object in a more dynamic fashion. The good news is that (as usual with e17 and the EFL) the hard work has already been done - there's a function in e_module.h that does exactly what we need:
EAPI const char *e_module_dir_get(E_Module *m);
With this function in our arsenal, we just need to have the E_Module available to get the path to where the module will be installed. As per esseine's tutorial you will recall that this infomation is passed to us when the e_modapi_init function is called, so I've just passed the E_Module to the _moon_face_new function as an argument:
static Moon_Face *
_moon_face_new(E_Container *con, E_Module *module)
{
char edje_path[PATH_MAX];
...
snprintf(edje_path, sizeof(edje_path),
"%s/moon.edj", e_module_dir_get(module));
edje_object_file_set(face->moon_object, edje_path, "moon/main");
...
That's better! Now we're only hardcoding the "moon.edj" name and the "moon/main" values, but for now we have no way of changing these, even if we wanted to, so it's sufficient for now (^_^).
Next and importantly - we need to call
evas_object_show with the new edje object as the argument. This is the function call that will actually make the edje visible on the screen.
evas_object_show(face->moon_object);
Subsequent to this I have some calls which I basically copied from the clock module:
evas_object_resize(face->moon_object, 96, 96);
edje_object_calc_force(face->moon_object);
edje_object_part_geometry_get(face->moon_object, "moon", &x, &y, &w, &h);
// blah blah
The first of these,
evas_object_resize, obviously resizes the moon_object to be 96 wide by 96 high. Using this function ensures that our module starts off with a reasonable size.
The next call,
edje_object_calc_force, somewhat
less obviously forces the edje to recalculate it's layout (although at least with this module I've not seen the interface display itself incorrectly after resizing - I may be missing the point with this...)
Thirdly we are grabbing the geometry of our edje object's "moon" part - but we haven't even discussed parts or edjes yet, so this is best skipped for now (and it's not actually required anyway).
Now, we could stop here, but if we did so, our Moon module (displaying it's dummy black rectangle interface) would be stuck at a fixed location on our dekstop background. The user wouldn't be able to move or resize the moon module to their personal liking, as they can for other modules like the clock:
That's not cool either. This is where we need to get our module set up as an client of the e_gadget manager.
The e_gadget manager component of the e17 window manager will be familiar to anyone who's bothered to re-arrange the layout of modules on their desktop. By setting our module up as a gadget manager client we'll get all the nice animated module resizing / relocating capabilities for free. The e_gadman_client function calls are actually pretty self explanatory, and what you do here will depend on the characteristics of your particular module, so I'm not going to go into any great details on that. Suffice it to say we set various properties of our client, which will affect how the interface can be resized and moved. But at a minimum we need to create the E_Gadman_Client, create a "change" event handler callback that defines how our client will respond to move / resize actions from the user, and finally tell it to load.
face->gmc = e_gadman_client_new(con->gadman);
...
e_gadman_client_change_func_set(face->gmc, _moon_face_cb_gmc_change, face);
e_gadman_client_load(face->gmc);
In the case of the moon module, the callback function (_moon_face_cb_gmc_change) is basically identical to that of the clock module. This function gets called in response to the user moving or resizing the module while the gadget edit mode is on. After setting up our gadman client, now look what we can do:
Wheeee! Our module now has all the shiny whizbangs available for resizing, and it's also relocatable now. Even our dummy edje looks pretty good with it registered with the gadget manager (^_^). This concludes the set up of a Moon_Face.
Finally, after all this excitement in the _moon_face_new function, we teardown everything we've done / deallocate memory in the _moon_face_free function.
7) And now back to that timer callback...So now we have plonked a module on the desktop. In fact, at this point you can't see it if you load and enable it as the user interface still doesn't exist yet. But, if you turn on the gadget edit mode, you'll see a empty border on your screen - this will be the moon's space.
But before getting ahead of ourselves, we need to fill out the details of the _moon_cb_check callback for the Moon object that we defined back up in the _moon_new function. The Ecore_Timer is going to go off every 60 seconds, and when that happens we need the function we defined to calculate the current moon phase, and then somehow inform our yet to be created user interface what the value is.
The callback is simple though because, as I mentioned earlier, the work has already been done for us previously in e16's E-MoonClock epplet and prior moon clock implementations. Here I created a new helper function, _moon_phase_calc, to return the moon phase. This helper function mimics the e16 epplet code, just cutting out some stuff that we don't need, and reformatting to my taste.
static double
_moon_phase_calc()
{
...
... (blah blah blah, read the source if you are interested in astronomy!)
...
return c.MoonPhase;
}
Now that we have the value representing the moon phase, we have to get this information to the edje of our moon_face (or faces, if you have a flasher setup than me).
Hmmmmmmmm! This is about the first original thing we've had to do in this module - pretty much everything else we have done so far is plagarise from clock and other modules. So how do we get our moon phase value to our edje? Rest assured,
the Rasterman has this base covered too.
8) Application -> Edje communicationsSo, we need our application code to be able to pass a message to our Edje interface, containing the float information representing the current phase of the moon.
After some poking around here and there, I discovered that Edje has a message queue interface, allowing applications to send data of various types to their interfaces. This is of course exactly what is required for the moon module. The API call of particular interest here is this one (
from Edje.h - currently undocumented other than in the source, although I'm going see what I can do about that):
EAPI void edje_object_message_send (Evas_Object *obj, Edje_Message_Type type, int id, void *msg);
To use this, first you need to determine what you want to send to your Edje. The various types currently available are declared in
Edje.h:
typedef struct _Edje_Message_String Edje_Message_String;
typedef struct _Edje_Message_Int Edje_Message_Int;
typedef struct _Edje_Message_Float Edje_Message_Float;
typedef struct _Edje_Message_String_Set Edje_Message_String_Set;
typedef struct _Edje_Message_Int_Set Edje_Message_Int_Set;
typedef struct _Edje_Message_Float_Set Edje_Message_Float_Set;
typedef struct _Edje_Message_String_Int Edje_Message_String_Int;
typedef struct _Edje_Message_String_Float Edje_Message_String_Float;
typedef struct _Edje_Message_String_Int_Set Edje_Message_String_Int_Set;
typedef struct _Edje_Message_String_Float_Set Edje_Message_String_Float_Set;
In this case we'll assume we're happy to just send a single floating point value to our Edje, so we'll use Edje_Message_Float. The struct for one of these is ultra simple:
struct _Edje_Message_Float
{
double val;
};
This is self-explanatory - Edje_Message_Float has just one element, a value, which we're going to assign to hold our moon phase information:
Edje_Message_Float msg;
...
msg.val = _moon_phase_calc();
...
edje_object_message_send(face->moon_object, EDJE_MESSAGE_FLOAT, 1, &msg)
But there's two more args defined there - EDJE_MESSAGE_FLOAT is also defined in a enum in Edje.h, but the '1' argument is less obvious. Later on when we look at how the Edje receives these messages, the use of this argument will become clear - but in brief, the problem is that the messaging interface doesn't cater for arbitrary function names to be called - there is only one function name. Additionally, it's only able to handle the predefined data types shown above. Now if you imagine that you had a module where you wanted to send two different messages containing two completely unrelated pieces of information - yet perhaps those different pieces of information were both represented using the same
type of value (say float), the Edje needs a way to distinguish one float message from the next. This numeric id provides a means of making such a distinguishment between different messages that use the same Edje Message Type. In our example, we only have one float, so this id value doesn't actually matter for us, and thus we'll just choose '1' as our value - we could choose anything else though.
With that, we're done with everything of interest in src/e_mod_main.h and src/e_mod_main.c.
9) Next timeIn the second part of this article (under construction!) we'll take a look at the EDC for the moon, how it receives this floating point value from the application. and bring everything together by displaying appropriate moon images on screen. We'll also take a look at the embedded embyro scripting functionality as well.
Labels: enlightenment