Maemo Coding Style and Programming Guidelines
General Overview
The purpose of this document is to set guidelines and recommendations for maemo developers and new program code written for the platform. This document assumes that programming is done in C language, but most of the guidelines are not language-specific. It is also required that developers possess a certain level of knowledge of Linux and GTK+ programming.
Why are programming guidelines needed? Some reasons for this are:
- Readability of code
- Many people write program code for maemo and have different coding styles because C allows it, therefore C programs can look very different and be difficult to read for someone who is used to a particular style.
- Correctness and robustness of code
- Some people are more disciplined programmers and others are more relaxed; this can lead to "sloppy programming" - that is, incomplete error handling, no sanity checks, hacking etc.
- Maintainability of code
- Many programmers tend
to write "ingenious" code which is almost impossible for others
to read, and thus almost impossible to maintain and debug. So, matters should be kept simple.
"Debugging is twice as hard as writing code in the first place. Therefore, if you write code as cleverly as possible, you are, by definition, not smart enough to debug it." (Brian W. Kernighan)
- Platform requirements
- Some guidelines must be followed by maemo developers, because they have direct impact on the platform (for example, power usage).
In addition to this document, one should read also the GNOME Programming Guidelines1 and pay attention to Glib/Gnome-specific issues. There is also a coding style document by Linus Torvalds for the Linux kernel2, containing hints on how to configure one's editor with specific indentation style used in the maemo code.
Even though proper coding style alone is not a guarantee of good software, it at least guarantees a good level of legibility and maintainability.
Maemo Coding Practices
Use the following maemo coding practices:
- The section Basics of Good Programming contains generic guidelines about coding style, variable usage and naming, error checking and many generic guidelines that good developers may already be familiar with. The most important thing about code style is consistency; one should pick a specific style and always follow it.
- Maemo developers must be careful with memory usage and memory leaks: one should always consider employing and initializing local variables. Code can be protected from memory leaks by freeing memory as soon as possible, or using automatic variables stored in a stack (not in a heap), guaranteeing automatic allocation and de-allocation.
- Memory pool should not be fragmented with malloc(3) and small or multiple instances of free(3). Reusing memory or freeing it in large blocks is recommended. A more effective method than manually searching for memory leaks is to use an automatic tool. For more information, see section Memory Usage.
- If planning to use threads, one should remember how difficult threads can be to debug. Threads should not be used just for their easiness or fashionability. They should only be used to isolate function calls in parts of the code. N.B. Asynchronous function calling can replace threads in many ways.
- System message notification and application-to-application communication make use of D-BUS, a lightweight message passing protocol. D-BUS must not be used to pass long or frequent messages between applications. N.B. D-BUS delivery is not guaranteed, but the application must check for it. For more information on D-BUS guidelines, see section D-BUS.
- When developing a new application in the maemo platform, it is the programmer's responsibility to catch hardware events, such as button press and touchpad actions, in the application. Touchpad events are similar to mouse events in a desktop environment. Hardware buttons form a special case, since they have special semantics, such as zooming, canceling operations and various other actions expected by maemo users. For more information on hardware events, see section Event Handling.
- Embedded devices offer many challenges, primarily for desktop developers. While a desktop user accepts a certain level of resource wasting, users of maemo devices do not accept such waste. One of the main objectives of maemo developers is to keep the resource usage at minimum. For a full review of this topic, see section Power Saving.
Basics of Good Programming
Most of the following conventions are useful for any programmer, and experienced programmers should already know them. However, there are also some that are related to the programming of embedded devices, such as avoiding the use of dynamic memory, prioritizing the reliability of code and, above all, saving resources.
Coding Style
It is more preferable to use simple than "clever" solutions, and one should always be wary of lazy coding and especially so-called hacks. Clever code is harder to read, thus harder to debug and more difficult to prove correct. Careful coding means taking care of all possible error situations and taking the time to think while writing the code.
Source code should be commented. The comments should be written in English. In general, comments describe what is being done, not how. Comments should be made based primarily on what functions do, and they should always be kept synchronized with the code. The formats (such as strings, files and input) should be described to make it easier to understand the code.
It is not advisable to use more than three or four indentation levels. When using tab characters for indentation, one should make sure that the code does not overflow from a terminal screen (width 80 columns) even if the tab size is 8. The functions should be kept short, no longer than 50—100 lines of code, unless absolutely necessary. The line length should be kept at between 1—75 characters.
Empty lines should be used to group code lines that logically belong together. One or two empty lines can be used to separate functions from each other. The command indent -kr <inputfile> -o <outputfile> can carry out some of these functions for the code.
Variable Usage
Local variables should always be favored over global variables, i.e. the visibility of a variable should be kept at minimum. Global variables should not be used to pass values between functions: this can be performed with arguments. N.B. Global variables require extra care when programming with threads. Thus, these should be avoided.
All variables should be initialized to a known illegal value (like 0xDEADBEEF) if they cannot be given a meaningful value from the beginning. There are at least two reasons for this:
- Known initial values contribute to predictable behavior in the program, i.e. they reduce random behavior in error situations caused by uninitialized memory. This helps in troubleshooting.
- Illegal initial values increase the probability of early detection of an erroneous state of the program, as an illegal value (such as a null pointer) is more easily detected than a random one.
Error Checking and Handling
The return codes of all system calls and functions that can potentially fail should be examined. Usually system calls only fail in rare situations, but these must also be dealt with. At least, it is good to have a log entry stating the reason for a program exit when the program cannot continue, instead of calling a segmentation fault when memory allocation has failed.
One should be prepared for network disconnections. If a program uses network connections, it must be written in a way that allows it to recover from lost connections and broken sockets. The program must operate correctly even when a disconnection occurs.
Variable and Function Naming
Descriptive, lower-case names should be used for variables and functions. Underscores (_) can be used to indicate word boundaries. Non-static and unnecessary global names should be avoided. If it is absolutely necessary to employ a global variable, type or function, one should use a prefix specific to the library or module where the name is, e.g. gtk_widget_.
Each variable should be employed for one purpose only. The optimization should be left to the compiler. Short names for local variables are fine when their use is simple, e.g. i, j and k for index variables. In functions with pointer arguments, const should be used when possible in order to indicate what causes changes (or does not cause any changes) in the pointed memory.
Constants and Header Files
So-called magic values should not be used, meaning literal constants (such as numbers) should not be used as coefficients for buffer sizes. In a header file, #define can be used to name them. That name can then be used in the code instead of the value. That way, it is easier to adjust constants later and make sure that all occurrences are changed when the constant is modified. However, names like "#define ONE 1" should be avoided. If there are a lot of constants, putting them into a separate header file should be considered.
Header files should be written to be protected against multi-inclusion. Also, it should be made sure that they work when included from C++ code; this is achieved by using #ifdef __cplusplus extern "C" { ... }, or by using G_BEGIN_DECLS ... G_END_DECLS in Glib/GTK+ code.
Memory Usage
Static memory (variables) should be preferred to dynamic memory, when the needed memory size is not large and/or is used regularly in the program. Static memory does not leak, a pointer to static memory is less likely to cause a segmentation fault, and using static memory keeps the memory usage more constant.
Unneeded dynamic memory should always be freed in order to save resources. N.B. Shared memory is not released automatically on program exit.
Memory heap fragmentation caused by mixing calls of free() and malloc() should be avoided. In a case where several lumps of allocated memory have to be freed and reallocated, the use of free() must be a single sequence, and the successive allocation another sequence.
Thread Usage
Threads can cause very nasty bugs, and the advantages offered by them are usually insignificant. Avoiding threads also makes it easier to use static memory. Threads should only be used to isolate portions that are considered untrusted and/or prone to failure (e.g. plug-ins, Bluetooth or Wireless scanning) from the main execution flow.
Maemo Conventions
This section contains a collection of coding-related guidelines that complement the generic rules in section Basics of Good Programming. Special attention should be paid to the error handling discussion in section Error Checking and Handling.
D-BUS
The D-BUS message bus is a central part of the platform architecture. D-BUS is primarily meant for lightweight asynchronous messaging, not for heavy data transmission. This section contains conventions concerning the use of D-BUS.
One of the design principles of D-BUS was that it must be lightweight and fast. However, it is not as lightweight and fast as a Unix socket. N.B. By default, D-BUS communication is not reliable. The system has a single D-BUS system bus that is used by many processes simultaneously. A big load on the system bus could probably slow down some GUI response times. To summarize:
- The D-BUS system bus should not be used for heavy messaging or regular transfers of large amounts of data, because it slows down the communication of other processes and requires extra processing power (and with it, electrical power). For that purpose, using a Unix socket, pipe, shared memory or file should be considered.
- Broadcasting on a D-BUS should be avoided, especially when there are a lot of listeners, such as on the system bus.
- When sending a message with, for example, dbus_connection_send(), it should not be assumed that the message is surely received by the server. Things can happen to make the message disappear on the way: the server can fail before receiving the message when the message is buffered in the client or in the daemon, or message buffer in the daemon or in the server can be full, resulting in discarding the message. Therefore, replies must be sent and received in order to make sure that the messages have been reliably conveyed.
- In D-BUS messages specified by the application framework (including Task Navigator, Control Panel, Status Bar, and some LibOSSO messages), UTF-8 encoding should be used as the character encoding for all string values.
- The libOSSO library of the application framework contains high-level functions (osso_rpc_*) that wrap the D-BUS API for message sending and receiving.
- All maemo applications need to be initialized with osso_initialize() function, connecting to the D-BUS session bus and the system bus. One symptom of missing initialization is that the application starts from Task Navigator, but closes automatically after a few seconds.
- The underscore (foo_bar) should be used instead of "fancy caps" (FooBar) in D-BUS names (such as methods and messages). The name of the service or the interface should not be repeated in the method and message names. The D-BUS method names should use a verb describing the function of the method when possible.
Applications should listen to D-BUS messages indicating the state of the device, such as "battery low" and "shutdown". When receiving these messages, the application may, for instance, ask the user to save any files that are open, or perform other similar actions to save the state of the application. There is also a specific system message, emitted when applications are required to close themselves. For more information about these topics, see Maemo 4.0 Tutorial3, section LibOSSO Library and Maemo Connectivity Architecture4, section D-Bus Connectivity UI interface.
Signal Handling
The application must exit() cleanly on the TERM signal, since this is the phase when GTK stores the clipboard contents to the clipboard manager, without losing them.
The TERM signal is sent, for example, when the desktop kills the application in the background (if the application has announced itself as killable).
Event Handling
The maemo user interface (Hildon) is based on GTK+ toolkit (the same as used in Gnome), so many aspects of maemo's event handling are similar to GTK+. There are a few points specific to maemo, since it is designed to be used with a touch screen. Some points to remember here are:
- It is not possible to move the cursor position in touch screen without pressing the button all the time. This difference may affect drawing applications or games, where the listening events need to be designed so that the user can click the left and right side of screen without ever moving with mouse from left to right.
- The touch screen can take only one kind of click, so only the left button of the mouse is used, leaving no functionality for the right button.
- When necessary to open menus using the right button, it is still possible to show them by keeping the button down for a little longer than a fast click. These kind of context-sensitive menus are supported by the platform. See the GTK+ widget tap and hold setup function in the Hildon API Documentation5.
- GtkWidget in maemo has been changed to pass a special insensitive_press signal when the user presses on an insensitive widget. This was added because of actions such as showing a banner when pressing disabled widgets, e.g. a menu.
Touch Screen Events
The main interaction with users in maemo is through touch screen events. The main actions are:
- Single tap
- Highlight and activate
- Stylus down and hold
- Drag and drop
- Panning
- Stylus down
- Stylus up
- Stylus down and cancel
Of course, all those touch screen actions are treated by GTK as regular mouse events, like button presses, motion and scrolling.
Minimizing Problems in X Applications
The application must not grab the X server, pointer or keyboard, because the user would not be able to use the rest of the device. Exceptions: System-UI application and Task Navigator, Status Bar, Application menu, context-sensitive menu and combo box pop-up widgets.
Applications must not carry out operations that may block, for example, checking for remote files to see whether to dim a menu item, while menus or pop-ups are open.
If the operation is allowed to block (for example, when the device gets out of WLAN or Bluetooth range), the whole device user interface freezes if blocking happens with the menu open. This kind of check must be performed before opening the menu or pop-up, or in a separate thread.
All application dialogs must be transient to one of the application windows, window group or other dialogs. Dialogs that are not transient to anything are interpreted as system modal dialogs and block the rest of the UI.
Hardware Button Events
Table 1. Hardware button events
Hardware key | Event |
---|---|
Home key | HILDON_HARDKEY_HOME |
Menu key | HILDON_HARDKEY_MENU |
Navigation keys | HILDON_HARDKEY_UP, HILDON_HARDKEY_DOWN, HILDON_HARDKEY_LEFT, HILDON_HARDKEY_RIGHT |
Select key | HILDON_HARDKEY_SELECT |
Cancel key | HILDON_HARDKEY_ESC |
Full screen key | HILDON_HARDKEY_FULLSCREEN |
Increase and decrease keys | HILDON_HARDKEY_INCREASE, HILDON_HARDKEY_DECREASE |
It is important not to use the GDK_* key codes directly; the HILDON_HARDKEY_* macros should be used instead (they are merely macros for the GDK key codes). This is because hardware keys may relate to different GDK key codes in different maemo hardware.
Also, the application must not act on keystrokes unless it truly is necessary. For example, Home and Fullscreen keys have system-wide meanings, and the application can be notified by other means that it is going to be moved to background or put in full screen mode.
For more on this topic, see the above-mentioned Hildon UI Guide, the Maemo 4.0 Tutorial, section Using Maemo Input Mechanism.
State Saving and Auto-Saving
State saving is a method to save the GUI state of an application, so that the same GUI can be rebuilt after the application has restarted. In practice, the state is saved into a file stored in volatile memory, therefore the saved state will not last over a reboot of the device.
The application must save its state on any of the following events:
- When the application moves from the foreground to the background of the GUI.
- Before the application exits due to a memory management measure.
The application must always load the saved state (if it exists) on start-up. However, the application can decide not to use the loaded state, by its own calculation. The saved state must always be usable by the application, even if the application is likely to make the decision not to use it.
Applications must implement auto-saving if they handle user data. User data is information that the user has entered into the application, usually for permanent storage, e.g. text in a note. Auto-saving ensures that the user does not lose too much unsaved data if the application or the system crashes, or the battery is removed. Auto-saving can also mean that a user does not have to explicitly save their data.
Auto-saving can be triggered by an event, e.g. the battery low event or system shutdown. The application must auto-save:
- When it closes (for example, because of a system shutdown or restart).
- When it is moved to the background.
- At regular, configurable intervals when the application is on top, unless it has recently been auto-saved due to another event.
- On receiving a message requesting auto-saving.
Changes made to dialogs are not auto-saved. Every application must provide an event handler to implement the fourth item. Naturally, the application does not have to auto-save when there is no new, unsaved data.
More information about state saving can be found in LibOSSO documentation7.
Configurability
Maemo applications must use GConf for configurations where changes need to be monitored. GConf is the configuration management system used in Gnome. It is advisable to read the documentation available on the GConf project page8.
Normal configurations (that do not need to be monitored) must be placed in a file under the ~/.osso/ folder. The file can be easily written or interpreted by the GKeyFile parser from Glib. Applications must have sensible defaults, ensuring that the application will function even if the configuration file is missing.
Using GConf
Naming GConf keys: first a new GConf directory (for GConf keys) should be made under the GConf directory /apps/Maemo for the program. For example, if the program is named tetris, the private GConf keys go under the GConf directory /apps/Maemo/tetris.
File System
File management operations for user files must be performed through an API provided by the application framework. In other words: the Unix file I/O API should not be used for files and folders that are visible to the user. N.B. The Unix (POSIX) file I/O API can still be used in other parts of the file system.
The main reason for this is that maemo needs to make the file management operations behave case-insensitively, even on the case-sensitive Linux file system. Additionally, the auto-naming of files and other such actions can be implemented in the application framework.
All applications are normally installed under the /usr directory, and must use the directory hierarchy described for /usr in Filesystem Hierarchy Standard (FHS)9. In addition to the directories specified in the document, the following are employed under /usr:
- share/icons/hicolor/<size>/apps for icon files
- share/sounds for sound files
- share/themes for GUI themes
Configuration files are placed under /etc/Maemo/<program name>. N.B. Some configurations are managed as described in section Configurability.
Variable data files that are not user-specific (but system-specific) must be created under /var, as suggested in Filesystem Hierarchy Standard.
User-specific files should preferably be located under the directory /home/user/apps/<program name>. The user's home directory /home/user can be used quite freely when there is no risk of a conflict. However, the directory /home/user/MyDocs is reserved for the user's own files (such as document, image, sound and video files), i.e. for files that are visible through the GUI.
The directory for the program temporary files is /tmp/<program name>/. To create a temporary file, mkstemp(3) immediately followed by unlink(2) is used. The reason for calling unlink(2) early is to ensure that the file is deleted even if the application crashes or is killed with the kill signal. Note that unlink(2) does not delete the file until the program exits or closes the file.
The available space on the main flash is very limited, and is very easily exhausted by applications or by the user (for example, by copying images, music or videos to it). If the flash is full, all the system calls trying to write to the disk fail, and the device is slower because JFFS2 file system needs some free space.
Therefore, no file can grow without limitation, and file sizes in general must be kept small. Applications must not write to flash when it is starting up, otherwise the write may fail and the application will not start.
Memory Profiling
Memory profiling is a difficult task and requires good tools. Great care must be taken considering memory leaks. Even though desktop users reboot the system often and memory leaks are less visible for them, maemo users expect to reboot the system less (or almost never), so even the smallest memory leak is noticeable.
For more information about variables and memory usage, see section Memory Usage. There are many memory tools available for profiling and finding memory leaks. Using Valgrind is recommended.
When compiling the program with debug and profile information, one should use standard profiling tools like gprof to find bottlenecks, as well as valgrind to find memory leaks or overruns. The XResTop tool (a top-like application to monitor X resource usage by clients) can also be used to monitor the leaking of X resources.
Secure Programming
This section lists conventions to be used to prevent security vulnerabilities in programs. At the same time, these can make programs more robust. For more information on secure programming, see Secure Programming for Linux and Unix HOWTO10 and Secure Programming11.
Hildon applications (such as GTK+) cannot run with elevated privileges, so that should be avoided in suid/sgid programs. If the applications really require higher privileges, the code must be split in two: a back-end should be created with higher privileges to communicate with the user interface through IPC.
These are the general rules for secure programming:
- Instead of system(3) and popen(3), the exec(3) family of functions should be used when executing programs. Also, shell access should not be given to the user.
- Buffer overflows must be prevented. See section Buffer Overflows.
- Backed-up data should be as secure as the original data, i.e. security and secrecy requirements for the back-up data must be the same as for the original data that was backed up.
- The default permissions for a new file (umask value) must have permissions for the user only.
- Temporary files must be created using mkstemp(3).
- Storing secrets, such as passwords, directly into the code should be avoided.
- One should not use crypt(3).
- One should not use higher privileges than necessary for the job at hand. Extra privileges should be dropped when they are no longer needed, if possible.
- The success of all system functions should always be checked, such as malloc(3).
- Random numbers for cryptography must be read from the /dev/random or /dev/urandom device.
Buffer Overflows
To avoid buffer overflows, safe versions of some system functions can be used. These can be seen in the table below. For Glib/GTK code, the Glib versions are best, e.g. g_strlcpy() guarantees that the result string is NULL-terminated even if the buffer is too small, while strncpy() does not.
Table 2. System functions
Unsafe | Safe | Best (GLib) |
---|---|---|
strcpy() | strncpy() | g_strlcpy() |
strcat() | strncat() | g_strlcat() |
sprintf() | snprintf() | g_snprintf() |
gets() | fgets() |
When using scanf(3), sscanf(3) or fscanf(3), the amount of data to be read should be limited by specifying the maximum length of the data in the format string. When reading an environment variable with getenv(3), no assumptions should be made of the size of the variable; it is easy to overflow a static buffer by copying the variable into it.
Packaging
All software above the system level is installed as Debian packages. It is recommended to read the Making an Application Package13, explaining the basic packaging process of Debian packages for maemo.
Power Saving
Modern CPUs are concerned about efficient power usage, and many techniques have been developed in order to reduce this power consumption. One of those techniques is CPU frequency reduction, which scales down the CPU's clock after a period of low usage.
Idle mode represents the CPU's state of minimum resource usage, and it is the best way available to save power. In this state, many core functions are disabled, and only when an external event happens, such as tapping on the screen, the CPU is awakened and starts consuming more energy.
The maemo developers must be aware that maemo platform employs these techniques to reduce power, and programs must be written so that they consume as little electrical energy as possible by means of giving maximum opportunity for the CPU to become idle.
Some useful tips:
- If the program is not processing or doing something useful, it should be left idle. Many toolkits (such as GTK+) have an idle() function that can be called when the program has nothing to do.
- The CPU should not be unnecessarily encumbered, as power consumption is proportional to the CPU load. Many processes running and competing for the CPU leads to more power waste.
- Periodical alarms or timers should not be used, unless absolutely necessary. One should consider what would be a reasonable period for updating a progress bar on the screen. Continuous (permanent) timers that tick faster than once in every five seconds must not be used.
- Busy-waiting
mode is not acceptable, even if it is busy waiting on empty loops or NOPs:
significant power saving is achieved only when the CPU is idle. The following example
shows how almost the same code can either prevent or support sleep.
Power Management Unfriendly code: SDL_EventState (SDL_SYSWMEVENT, SDL_ENABLE); while (!done) { SDL_WaitEvent(0); while (SDL_PollEvent (&event)) { if (event.type == SDL_SYSWMEVENT) { .... here, for example, check event.syswm.msg->event.xevent.type etc ... } } Power Management Friendly code: while(wait_outcome = SDL_WaitEvent(0)) { SDL_PollEvent (&event); if (event.type == SDL_SYSWMEVENT) { .... here for example check event.syswm.msg->event.xevent.type etc ... break; // <----NOTE THIS } } if(wait_outcome == 0) handle_sdl_error();
This optimization also provides power management friendly code with improved responsiveness, because it prevents a waiting application from monopolizing the CPU, leaving machine time to concurrent processes. - All UI timers should be stopped when moving to the background, and when the screen is turned off. Application-specific exceptions for this rule can be granted when necessary.
- Updating the GUI should be avoided when the application is running on the background, or when the screen has been turned off. Unnecessary graphical elements or constantly updated screen components should also be removed.
- Doing polling for a resource wastes CPU and power; instead, asynchronous notifications sent by a separate daemon should be used.
- Small writes to file system should be grouped into bigger chunks.
- The usage of remote peripherals, such as Bluetooth or Wireless, should be minimized. It is advisable to keep values in cache and avoid repeated and/or redundant queries to such devices. The use of hardware in general should be minimized. This refers to e.g. screen updates or external storage devices.
For more information, see Software Matters for Power Consumption14.
References
[1] GNOME Programming Guidelines, by Federico Mena Quintero, Miguel de Icaza and Morten Welinder: http://developer.gnome.org/doc/guides/programming-guidelines/book1.html
[2] Linux kernel coding style: http://pantransit.reptiles.org/prog/CodingStyle.html
[3] Maemo 4.0 Tutorial: http://maemo.org/development/documentation/tutorials/maemo_4-0_tutorial.html
[4] Maemo Connectivity Architecture: http://maemo.org/development/documentation/how-tos/4-x/maemo_connectivity_guide.html
[7]LibOSSO API
[8] GConf configuration system project page: http://www.gnome.org/projects/gconf/
[9] Filesystem Hierarchy Standard (FHS), maintained by freestandards.org: http://www.pathname.com/fhs/
[10] Secure Programming for Linux and Unix HOWTO, by David A. Wheeler: http://www.dwheeler.com/secure-programs/
[11] Secure Programming, maintained by Oliver Friedrichs (of@securityfocus.com): http://marcin.owsiany.pl/sec/secure-programming.html
[13] Making an application package: http://maemo.org/development/documentation/how-tos/4-x/howto_making_an_application_package.html
[14] Software Matters for Power Consumption, by Nathan Tennies on Embedded.com: http://www.embedded.com/story/OEG20030121S0057
[15] Secure Programming Cookbook for C and C++: Recipes for Cryptography, Authentication, Input Validation & More: http://books.slashdot.org/article.pl?sid=03/09/30/1644222
Improve this page